import assert from 'node:assert/strict' import { readFile } from 'node:fs/promises' import { createHash } from 'node:crypto' import { execFileSync } from 'node:child_process' import { parse } from '@babel/parser' import postcss, { type Rule } from 'postcss' import { Rw, zw, Bw, Vw, JQe, DETAIL_NARROW_WIDTH, SIDEBAR_NARROW_WIDTH, DETAIL_PANEL_MOTION, SOURCE_PANEL_LAYOUT_EVIDENCE, normalizeInset, clampSidebarWidth, resolveSidebarDragAction, resolveChatAreaAvailableWidth, resolveChatAreaWidth, resolveDetailDrawerWidth, resolveDetailPanelLayoutWidths, resolveRememberedDetailWidth, resolveLeadingInsetRebalanceWidth, getNarrowState, resolveNarrowState, stickyNarrow } from '../src/renderer/src/workbuddy/workbench/source-panel-layout' const hash = (value: string): string => createHash('sha256').update(value).digest('hex') assert.deepEqual(Rw, { EXPANDED_WIDTH: 280, MIN_WIDTH: 240, MAX_WIDTH: 360, COLLAPSE_THRESHOLD: 120, EXPAND_THRESHOLD: 150, COLLAPSED_WIDTH: 48 }) assert.deepEqual(zw, { MIN_WIDTH: 360, PREFERRED_WIDTH: 600 }) assert.deepEqual(Bw, { BREAKPOINT: 600, CHAT_MIN_WIDTH: 360 }) assert.deepEqual(Vw, { MIN_WIDTH: 360, DEFAULT_WIDTH: 500, OVERVIEW_ONLY_WIDTH: 318, MAX_WIDTH: Infinity }) assert.deepEqual(JQe, { WIDTH: 2 }) assert.equal(DETAIL_NARROW_WIDTH, 1000) assert.equal(SIDEBAR_NARROW_WIDTH, 640) assert.deepEqual(DETAIL_PANEL_MOTION, { openMs: 200, closeMs: 150, openEasing: 'cubic-bezier(0, 0, .2, 1)', closeEasing: 'cubic-bezier(.4, 0, 1, 1)' }) assert.equal(normalizeInset(Number.NaN), 0) assert.equal(normalizeInset(Infinity), 0) assert.equal(normalizeInset(-1), 0) assert.equal(normalizeInset(31.9), 31) assert.equal(clampSidebarWidth(48), 240) assert.equal(clampSidebarWidth(280), 280) assert.equal(clampSidebarWidth(500), 360) assert.equal(resolveSidebarDragAction(119, false), 'collapse') assert.equal(resolveSidebarDragAction(120, false), 'resize') assert.equal(resolveSidebarDragAction(150, true), 'keep-collapsed') assert.equal(resolveSidebarDragAction(151, true), 'expand') assert.equal(resolveChatAreaAvailableWidth({ containerWidth: 1336, sidebarWidth: 280 }), 1056) assert.equal(resolveChatAreaAvailableWidth({ containerWidth: 1336, sidebarWidth: 280, leadingInset: 100 }), 956) assert.equal(resolveChatAreaAvailableWidth({ containerWidth: 100, sidebarWidth: 280, leadingInset: 100 }), 0) assert.equal(resolveChatAreaWidth(1336, 100), 1236) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1336, sidebarWidth: 280, requestedDetailWidth: 528 }), { mainWidth: 528, detailWidth: 528 }) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1336, sidebarWidth: 280, requestedDetailWidth: 647 }), { mainWidth: 409, detailWidth: 647 }) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1336, sidebarWidth: 280, requestedDetailWidth: 900 }), { mainWidth: 360, detailWidth: 696 }) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1336, sidebarWidth: 280, leadingInset: 100, requestedDetailWidth: 900 }), { mainWidth: 460, detailWidth: 596 }) assert.equal(resolveDetailPanelLayoutWidths({ containerWidth: 999, sidebarWidth: 280, requestedDetailWidth: 360 }), undefined) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1000, sidebarWidth: 280, requestedDetailWidth: 318 }), { mainWidth: 360, detailWidth: 360 }) assert.deepEqual(resolveDetailPanelLayoutWidths({ containerWidth: 1001, sidebarWidth: 280, requestedDetailWidth: 360.9 }), { mainWidth: 361, detailWidth: 360 }) assert.equal(resolveDetailDrawerWidth({ containerWidth: 1336, sidebarWidth: 280 }), 528) assert.equal(resolveDetailDrawerWidth({ containerWidth: 900, sidebarWidth: 280 }), 360) assert.equal(resolveDetailDrawerWidth({ containerWidth: 900, sidebarWidth: 280, preferredWidth: 500.9 }), 500.9) assert.equal(resolveDetailDrawerWidth({ containerWidth: 900, sidebarWidth: 280, preferredWidth: 1200 }), 900) assert.equal(resolveDetailDrawerWidth({ containerWidth: 599, sidebarWidth: 280, isMobile: true, preferredWidth: 360 }), 599) assert.equal(resolveDetailDrawerWidth({ containerWidth: 300, sidebarWidth: 0 }), 300) assert.equal(resolveDetailDrawerWidth({ containerWidth: 0, sidebarWidth: 280 }), 0) assert.equal(resolveRememberedDetailWidth([undefined, null, 0, 360, 700.9, 800], 1056), 700) assert.equal(resolveRememberedDetailWidth([undefined, null, 360], 1057), 528) assert.equal(resolveRememberedDetailWidth([360.9, 700], 1056), 360, 'source checks >360 before flooring') assert.equal(resolveRememberedDetailWidth([Infinity], 1056), Infinity, 'source behavior is preserved; caller owns finite stored-value validation') const rebalance = { previousInset: 0, nextInset: 48, isNarrowForDetail: false, isPanelVisible: true, rememberedDetailWidth: null, chatAreaWidth: 1001 } assert.equal(resolveLeadingInsetRebalanceWidth(rebalance), 500) assert.equal(resolveLeadingInsetRebalanceWidth({ ...rebalance, previousInset: 48 }), undefined) assert.equal(resolveLeadingInsetRebalanceWidth({ ...rebalance, isNarrowForDetail: true }), undefined) assert.equal(resolveLeadingInsetRebalanceWidth({ ...rebalance, isPanelVisible: false }), undefined) assert.equal(resolveLeadingInsetRebalanceWidth({ ...rebalance, rememberedDetailWidth: 500 }), undefined) assert.deepEqual(getNarrowState(1000), { isMobile: false, isNarrowForDetail: false, isNarrowForSidebar: false, screenWidth: 1000 }) assert.equal(getNarrowState(999).isNarrowForDetail, true) assert.equal(getNarrowState(1047, 48).isNarrowForDetail, true) assert.equal(getNarrowState(1048, 48).isNarrowForDetail, false) assert.equal(getNarrowState(639).isNarrowForSidebar, true) assert.equal(getNarrowState(640).isNarrowForSidebar, false) assert.equal(getNarrowState(599).isMobile, true) assert.equal(getNarrowState(600).isMobile, false) assert.equal(stickyNarrow(false, 1000, 1000), false) assert.equal(stickyNarrow(true, 1023, 1000), true) assert.equal(stickyNarrow(true, 1024, 1000), false) assert.equal(resolveNarrowState(getNarrowState(999), 1023).isNarrowForDetail, true) assert.equal(resolveNarrowState(getNarrowState(999), 1024).isNarrowForDetail, false) assert.equal(resolveNarrowState(getNarrowState(639), 663).isNarrowForSidebar, true) assert.equal(resolveNarrowState(getNarrowState(639), 664).isNarrowForSidebar, false) assert.equal(resolveNarrowState(getNarrowState(599), 623).isMobile, true) assert.equal(resolveNarrowState(getNarrowState(599), 624).isMobile, false) assert.deepEqual(resolveNarrowState(undefined, 1000), getNarrowState(1000)) assert.deepEqual(resolveNarrowState(null, 1048, 48), getNarrowState(1048, 48)) let geometryCases = 0 for (let containerWidth = 240; containerWidth <= 1920; containerWidth += 17) { for (const sidebarWidth of [0, 48, 240, 280, 360]) for (const leadingInset of [0, 1, 31, 128]) { let previous = -Infinity for (const requestedDetailWidth of [-100, 0, 318, 360, 500.9, 10_000]) { const result = resolveDetailPanelLayoutWidths({ containerWidth, sidebarWidth, leadingInset, requestedDetailWidth }) if (containerWidth - sidebarWidth - leadingInset < 720) assert.equal(result, undefined) else { assert.ok(result) assert.equal(result.mainWidth + result.detailWidth, containerWidth - sidebarWidth, 'space is conserved') assert.ok(result.mainWidth - leadingInset >= 360, 'minimum chat width remains after inset') assert.ok(result.detailWidth >= 360, 'minimum detail width remains') assert.ok(result.detailWidth >= previous, 'increasing requested width never shrinks the panel') assert.ok(Number.isInteger(result.detailWidth) && Number.isInteger(result.mainWidth)) previous = result.detailWidth } geometryCases++ } } } const recoveredText = await readFile('src/renderer/src/workbuddy/workbench/source-panel-layout.ts', 'utf8') const recoveredAst = parse(recoveredText, { sourceType: 'module', plugins: ['typescript'] }) const expectedBodies = SOURCE_PANEL_LAYOUT_EVIDENCE.functions as Record let bodyCount = 0, constantCount = 0 for (const statement of recoveredAst.program.body) { if (statement.type !== 'ExportNamedDeclaration') continue const declaration = statement.declaration if (declaration?.type === 'FunctionDeclaration' && declaration.id && expectedBodies[declaration.id.name]) { assert.equal(hash(recoveredText.slice(declaration.body.start!, declaration.body.end!)), expectedBodies[declaration.id.name].bodySha256, `source body preserved: ${declaration.id.name}`) bodyCount++ } if (declaration?.type === 'VariableDeclaration') for (const variable of declaration.declarations) { if (variable.id.type !== 'Identifier') continue const expected = (SOURCE_PANEL_LAYOUT_EVIDENCE.constants as Record)[variable.id.name] if (!expected || !variable.init) continue const expression = variable.init.type === 'TSAsExpression' ? variable.init.expression : variable.init assert.equal(hash(recoveredText.slice(expression.start!, expression.end!)), expected, `source constant preserved: ${variable.id.name}`) constantCount++ } } assert.equal(bodyCount, 12) assert.equal(constantCount, 7) const sourceCssText = await readFile('.tmp/rea-css-recovered/desktop-sdk-setup-Bh0rg_xP.css', 'utf8') assert.equal(hash(sourceCssText), SOURCE_PANEL_LAYOUT_EVIDENCE.motionSha256) const generatedCssText = await readFile('src/renderer/src/workbuddy/workbench/panel-motion-source.css', 'utf8') const sourceCss = postcss.parse(sourceCssText), generatedCss = postcss.parse(generatedCssText) const selected = (selector: string): boolean => /\.detail-panel-motion-[\w-]+|\.detail-panel-container(?:[\s.:#[]|$)/.test(selector) function describe(rule: Rule, source: boolean): unknown { const conditions: string[] = [] let ancestor = rule.parent while (ancestor) { if (ancestor.type === 'atrule') conditions.unshift(`@${ancestor.name} ${ancestor.params}`); ancestor = ancestor.parent } return { selectors: rule.selectors.filter(selector => !source || selected(selector)).map(selector => source ? selector.startsWith('.detail-panel-motion-') ? `.source-detail-layout${selector}` : `.source-detail-layout ${selector}` : selector), declarations: rule.nodes.filter(node => node.type === 'decl').map(node => ({ property: node.prop, value: node.value, important: !!node.important })), conditions } } const expectedRules: unknown[] = [], actualRules: unknown[] = [] sourceCss.walkRules(rule => { if (rule.selectors.some(selected)) expectedRules.push(describe(rule, true)) }) generatedCss.walkRules(rule => { if (rule.selector !== '.source-detail-layout') actualRules.push(describe(rule, false)) }) assert.equal(actualRules.length, 16) assert.deepEqual(actualRules, expectedRules, 'source motion declarations, order, selector membership and media conditions remain intact') const reduced = actualRules.filter(rule => (rule as { conditions: string[] }).conditions.includes('@media (prefers-reduced-motion:reduce)')) assert.equal(reduced.length, 1, 'transition:none must remain conditional on reduced motion') assert.ok(generatedCssText.includes('--wb-motion-duration-medium:.2s')) assert.ok(generatedCssText.includes('--wb-motion-duration-fast:.15s')) assert.ok(generatedCssText.includes('--wb-motion-easing-decelerate:cubic-bezier(0, 0, .2, 1)')) assert.ok(generatedCssText.includes('--wb-motion-easing-accelerate:cubic-bezier(.4, 0, 1, 1)')) execFileSync(process.execPath, ['scripts/rea/recover-panel-layout.mjs', '--check'], { stdio: 'pipe' }) console.log(JSON.stringify({ result: 'PASS source panel layout', geometryCases, sourceBodies: bodyCount, sourceConstants: constantCount, sourceCssRules: actualRules.length, checks: ['source SHA/body identity', 'source CSS/media identity', 'finite geometry invariants', 'sidebar drag thresholds', 'drawer/mobile width', 'remembered widths', 'leading inset', 'narrow 24px hysteresis', 'motion token values', 'reproducible generation'] }))