mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-09 20:48:29 +00:00
fix(app): reserve review pane minimum instead of capping chat width at 45% (#35078)
This commit is contained in:
parent
d8a57ee15c
commit
dd25d143c5
3 changed files with 112 additions and 4 deletions
|
|
@ -11,6 +11,7 @@ import {
|
|||
createMemo,
|
||||
createEffect,
|
||||
createComputed,
|
||||
createSignal,
|
||||
on,
|
||||
onMount,
|
||||
type ParentProps,
|
||||
|
|
@ -69,6 +70,11 @@ import { createTimelineModel } from "@/pages/session/timeline/model"
|
|||
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { syncSessionModel } from "@/pages/session/session-model-helpers"
|
||||
import {
|
||||
clampSessionPanelWidth,
|
||||
SESSION_PANEL_WIDTH_MIN,
|
||||
sessionPanelWidthMax,
|
||||
} from "@/pages/session/session-panel-width"
|
||||
import { SessionSidePanel } from "@/pages/session/session-side-panel"
|
||||
import { sessionPanelLayout } from "@/pages/session/session-panel-layout"
|
||||
import { SessionReviewEmptyChangesV2 } from "@opencode-ai/session-ui/v2/session-review-empty-changes-v2"
|
||||
|
|
@ -442,9 +448,39 @@ export default function Page() {
|
|||
newSessionDesign() ? desktopV2ReviewOpen() || desktopTerminalOpen() : desktopReviewOpen(),
|
||||
)
|
||||
const desktopSidePanelOpen = createMemo(() => desktopSessionResizeOpen() || desktopFileTreeOpen())
|
||||
let panelRow: HTMLDivElement | undefined
|
||||
const [panelRowWidth, setPanelRowWidth] = createSignal<number>()
|
||||
createResizeObserver(
|
||||
() => panelRow,
|
||||
({ width }) => setPanelRowWidth(width),
|
||||
)
|
||||
const splitReview = createMemo(
|
||||
() => (newSessionDesign() ? desktopV2ReviewOpen() : desktopReviewOpen()) && layout.review.diffStyle() === "split",
|
||||
)
|
||||
// The observer reports the content-box width, which already excludes the row
|
||||
// padding; only the flex gap between the panels remains to subtract.
|
||||
const sessionPanelAvailable = createMemo(() => {
|
||||
const width = panelRowWidth()
|
||||
if (width === undefined) return undefined
|
||||
return width - (settings.general.newLayoutDesigns() ? 8 : 0)
|
||||
})
|
||||
const sessionPanelMax = createMemo(() => {
|
||||
const available = sessionPanelAvailable()
|
||||
if (available === undefined) return 1000
|
||||
return sessionPanelWidthMax({ available, split: splitReview() })
|
||||
})
|
||||
// Clamp at render time so window or sidebar resizes squeeze the chat panel
|
||||
// instead of the review pane, without overwriting the persisted width.
|
||||
const sessionPanelResizedWidth = createMemo(() =>
|
||||
clampSessionPanelWidth({
|
||||
width: layout.session.width(),
|
||||
available: sessionPanelAvailable(),
|
||||
split: splitReview(),
|
||||
}),
|
||||
)
|
||||
const sessionPanelWidth = createMemo(() => {
|
||||
if (!desktopSidePanelOpen()) return "100%"
|
||||
if (desktopSessionResizeOpen()) return `${layout.session.width()}px`
|
||||
if (desktopSessionResizeOpen()) return `${sessionPanelResizedWidth()}px`
|
||||
return `calc(100% - ${layout.fileTree.width()}px)`
|
||||
})
|
||||
const centered = createMemo(() => isDesktop() && !desktopReviewOpen())
|
||||
|
|
@ -2148,6 +2184,7 @@ export default function Page() {
|
|||
<SessionRouteFrame>
|
||||
<SessionHeader />
|
||||
<div
|
||||
ref={panelRow}
|
||||
class="flex-1 min-h-0 flex flex-col md:flex-row"
|
||||
classList={{
|
||||
"gap-2 p-2": settings.general.newLayoutDesigns(),
|
||||
|
|
@ -2186,9 +2223,9 @@ export default function Page() {
|
|||
"-right-1": settings.general.newLayoutDesigns(),
|
||||
}}
|
||||
direction="horizontal"
|
||||
size={layout.session.width()}
|
||||
min={450}
|
||||
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.45}
|
||||
size={sessionPanelResizedWidth()}
|
||||
min={SESSION_PANEL_WIDTH_MIN}
|
||||
max={sessionPanelMax()}
|
||||
onResize={(width) => {
|
||||
size.touch()
|
||||
layout.session.resize(width)
|
||||
|
|
|
|||
52
packages/app/src/pages/session/session-panel-width.test.ts
Normal file
52
packages/app/src/pages/session/session-panel-width.test.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
clampSessionPanelWidth,
|
||||
REVIEW_PANE_WIDTH_MIN,
|
||||
REVIEW_PANE_WIDTH_MIN_SPLIT,
|
||||
SESSION_PANEL_WIDTH_MIN,
|
||||
sessionPanelWidthMax,
|
||||
} from "./session-panel-width"
|
||||
|
||||
describe("sessionPanelWidthMax", () => {
|
||||
test("reserves the unified review pane minimum", () => {
|
||||
expect(sessionPanelWidthMax({ available: 1700, split: false })).toBe(1700 - REVIEW_PANE_WIDTH_MIN)
|
||||
})
|
||||
|
||||
test("reserves a larger minimum for split diffs", () => {
|
||||
expect(sessionPanelWidthMax({ available: 1700, split: true })).toBe(1700 - REVIEW_PANE_WIDTH_MIN_SPLIT)
|
||||
expect(REVIEW_PANE_WIDTH_MIN_SPLIT).toBeGreaterThan(REVIEW_PANE_WIDTH_MIN)
|
||||
})
|
||||
|
||||
test("lets the chat panel take everything beyond the review pane minimum", () => {
|
||||
// Regression: the old cap was 45% of the window, forcing the review pane
|
||||
// to at least 55% of the window regardless of content.
|
||||
const available = 3440
|
||||
expect(sessionPanelWidthMax({ available, split: false })).toBeGreaterThan(available * 0.45)
|
||||
})
|
||||
|
||||
test("never drops below the chat panel minimum on small windows", () => {
|
||||
expect(sessionPanelWidthMax({ available: 600, split: true })).toBe(SESSION_PANEL_WIDTH_MIN)
|
||||
expect(sessionPanelWidthMax({ available: 0, split: false })).toBe(SESSION_PANEL_WIDTH_MIN)
|
||||
})
|
||||
})
|
||||
|
||||
describe("clampSessionPanelWidth", () => {
|
||||
test("keeps widths already within the limit", () => {
|
||||
expect(clampSessionPanelWidth({ width: 800, available: 1700, split: false })).toBe(800)
|
||||
})
|
||||
|
||||
test("forces the width down when the window shrinks", () => {
|
||||
expect(clampSessionPanelWidth({ width: 1600, available: 1700, split: false })).toBe(1700 - REVIEW_PANE_WIDTH_MIN)
|
||||
expect(clampSessionPanelWidth({ width: 1600, available: 1700, split: true })).toBe(
|
||||
1700 - REVIEW_PANE_WIDTH_MIN_SPLIT,
|
||||
)
|
||||
})
|
||||
|
||||
test("holds the chat panel minimum when there is no room for both", () => {
|
||||
expect(clampSessionPanelWidth({ width: 1600, available: 700, split: true })).toBe(SESSION_PANEL_WIDTH_MIN)
|
||||
})
|
||||
|
||||
test("skips clamping before the layout is measured", () => {
|
||||
expect(clampSessionPanelWidth({ width: 1600, available: undefined, split: false })).toBe(1600)
|
||||
})
|
||||
})
|
||||
19
packages/app/src/pages/session/session-panel-width.ts
Normal file
19
packages/app/src/pages/session/session-panel-width.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// The review pane has no width of its own: it takes whatever the chat panel
|
||||
// leaves behind. Instead of capping the chat panel at a fraction of the window
|
||||
// (which forces the review pane to grow with the monitor), reserve a fixed
|
||||
// minimum for the review pane and let the chat panel take everything else.
|
||||
export const SESSION_PANEL_WIDTH_MIN = 450
|
||||
export const REVIEW_PANE_WIDTH_MIN = 480
|
||||
export const REVIEW_PANE_WIDTH_MIN_SPLIT = 800
|
||||
|
||||
export function sessionPanelWidthMax(input: { available: number; split: boolean }) {
|
||||
const pane = input.split ? REVIEW_PANE_WIDTH_MIN_SPLIT : REVIEW_PANE_WIDTH_MIN
|
||||
return Math.max(SESSION_PANEL_WIDTH_MIN, input.available - pane)
|
||||
}
|
||||
|
||||
// `available` is undefined until the layout row is first measured; render the
|
||||
// stored width untouched until then to avoid a first-frame snap.
|
||||
export function clampSessionPanelWidth(input: { width: number; available: number | undefined; split: boolean }) {
|
||||
if (input.available === undefined) return input.width
|
||||
return Math.min(input.width, sessionPanelWidthMax({ available: input.available, split: input.split }))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue