Files
Agent-System-Frontend/src/workbenchLayout.ts
T
weichen 411a79d9e2 Deliver a conversation-first progressive workbench with safe live state
Why: make material, background work, confirmations and files accessible to nontechnical users across desktop and mobile.

Evidence: Dev SELF receipts for Frontend tsc, 190 tests, knip baseline, build, nine fixture browser cases and two real HTTP/browser smoke cases using MockRunner.

Impact: existing API and permissions remain authoritative; stale events/queries and duplicate effects are guarded, while one file preview and reading state are preserved.

Refs: work-companion-ui design r0.2 and plan r1 U2-U5; UI01-UI13.
2026-10-12 16:05:13 +00:00

70 lines
3.1 KiB
TypeScript

export interface WorkbenchLayoutOptions {
width: number
contextOpen: boolean
leftCollapsed: boolean
rightCollapsed: boolean
leftWidth: number
rightWidth: number
previewWidth: number
rightTab: 'work' | 'files'
filesView: 'list' | 'preview'
previewOpen: boolean
previewExpanded: boolean
}
export type WorkbenchPane = 'context' | 'conversation' | 'work' | 'files'
const COMPACT_WORKBENCH_WIDTH = 1024
function boundedWidth(value: number, fallback: number, min: number, max: number) {
return Math.min(max, Math.max(min, Number.isFinite(value) ? value : fallback))
}
// Only the rendered widths are bounded. Saved desktop preferences stay in the store.
export function selectWorkbenchLayout(options: WorkbenchLayoutOptions) {
const width = Number.isFinite(options.width) ? Math.max(0, options.width) : 1440
const available = Math.max(0, width - 8)
const compact = width < COMPACT_WORKBENCH_WIDTH
const filesWide = options.rightTab === 'files' && options.filesView === 'preview' && options.previewOpen
const previewExpanded = filesWide && options.previewExpanded && !options.rightCollapsed
if (compact) {
const pane: WorkbenchPane = options.contextOpen ? 'context' : options.rightCollapsed ? 'conversation' : options.rightTab
return {
compact, pane, showContext: pane === 'context', showConversation: pane === 'conversation',
showRight: pane === 'work' || pane === 'files', contextCollapsed: false, previewExpanded,
leftWidth: pane === 'context' ? available : 0,
rightWidth: pane === 'work' || pane === 'files' ? available : 0,
leftDivider: false, rightDivider: false,
}
}
const showRight = !options.rightCollapsed
const leftDivider = !options.leftCollapsed
const rightDivider = showRight && !previewExpanded
const panelCount = 1 + Number(!previewExpanded) + Number(showRight)
const gutters = (panelCount - 1) * 4 + (Number(leftDivider) + Number(rightDivider)) * 8
const leftMin = options.leftCollapsed ? 52 : 220
const rightMin = showRight ? filesWide ? 420 : 280 : 0
let leftWidth = options.leftCollapsed ? 52 : boundedWidth(options.leftWidth, 260, leftMin, 420)
let rightWidth = showRight
? filesWide ? boundedWidth(options.previewWidth, 700, rightMin, 1100) : boundedWidth(options.rightWidth, 360, rightMin, 560)
: 0
if (previewExpanded) rightWidth = available - gutters - leftWidth
else {
const conversationMin = filesWide && showRight ? 320 : 360
const budget = available - gutters - conversationMin
if (leftWidth + rightWidth > budget) {
const extraLeft = leftWidth - leftMin
const extraRight = rightWidth - rightMin
const ratio = Math.max(0, budget - leftMin - rightMin) / (extraLeft + extraRight || 1)
leftWidth = leftMin + Math.floor(extraLeft * ratio)
rightWidth = rightMin + Math.floor(extraRight * ratio)
}
}
const pane: WorkbenchPane = previewExpanded ? 'files' : 'conversation'
return {
compact, pane,
showContext: true, showConversation: !previewExpanded, showRight, contextCollapsed: options.leftCollapsed,
previewExpanded, leftWidth, rightWidth, leftDivider, rightDivider,
}
}