// Recover pure panel geometry and source motion CSS from fixed REA artifacts only. import { readFile, writeFile } from 'node:fs/promises' import { createHash } from 'node:crypto' import { resolve } from 'node:path' import { parse } from '@babel/parser' import postcss from 'postcss' const runtimeEvidenceId = 'ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb' const cssEvidenceId = 'ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94' const runtimeFile = 'contexts-BMeWSUHk.js' const runtimeSha256 = '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b' const motionFile = 'desktop-sdk-setup-Bh0rg_xP.css' const motionSha256 = 'b2ee58c0caf560eeddc4eb8c91d86cabddbec634b8d37ed5d6b85d0644c0e142' const tokenFile = 'tokens-Df-i-zsO.css' const tokenSha256 = '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696' const hash = text => createHash('sha256').update(text).digest('hex') const runtime = JSON.parse(await readFile(resolve('.tmp/rea-runtime-scripts.json'), 'utf8')) if (runtime.evidence_id !== runtimeEvidenceId) throw new Error('Fixed runtime Evidence changed') const matches = runtime.items.filter(item => item.file_path?.replaceAll('\\', '/').endsWith(`/renderer/assets/${runtimeFile}`)) if (matches.length !== 1) throw new Error('Expected exactly one panel layout runtime source') const artifact = matches[0].source?.artifact if (!matches[0].source.included || !artifact?.text || hash(artifact.text) !== runtimeSha256 || artifact.sha256 !== runtimeSha256) throw new Error('Fixed panel runtime SHA-256 mismatch') const source = artifact.text const ast = parse(source, { sourceType: 'module' }) const constantNames = ['Rw', 'zw', 'Bw', 'Vw', 'JQe', 'f$e', 'p$e'] const definitions = { normalizeInset: { parameters: 'e: number', result: 'number' }, clampSidebarWidth: { parameters: 'e: number', result: 'number' }, resolveSidebarDragAction: { parameters: 'e: number, t: boolean', result: "'expand' | 'keep-collapsed' | 'collapse' | 'resize'" }, resolveChatAreaAvailableWidth: { parameters: 'e: PanelAvailableWidthInput', result: 'number' }, resolveDetailDrawerWidth: { parameters: 'e: DetailDrawerWidthInput', result: 'number' }, resolveLeadingInsetRebalanceWidth: { parameters: 'e: LeadingInsetRebalanceInput', result: 'number | undefined' }, resolveDetailPanelLayoutWidths: { parameters: 'e: DetailPanelLayoutInput', result: 'DetailPanelLayoutWidths | undefined' }, resolveRememberedDetailWidth: { parameters: 'e: readonly (number | null | undefined)[], t: number', implementationParameters: 'e: readonly any[], t: number', result: 'number' }, resolveChatAreaWidth: { parameters: 'e: number, t: number', result: 'number' }, getNarrowState: { parameters: 'e: number, t: number = 0', result: 'NarrowLayoutState' }, stickyNarrow: { parameters: 'e: boolean, t: number, n: number', result: 'boolean' }, resolveNarrowState: { parameters: 'e: NarrowLayoutState | null | undefined, t: number, n: number = 0', result: 'NarrowLayoutState' } } const functions = new Map(), constants = new Map() function walk(node) { if (!node || typeof node !== 'object') return if (node.type === 'FunctionDeclaration' && definitions[node.id?.name]) { if (functions.has(node.id.name)) throw new Error(`Duplicate source function ${node.id.name}`) functions.set(node.id.name, node) } if (node.type === 'AssignmentExpression' && node.left.type === 'Identifier' && constantNames.includes(node.left.name) && (node.right.type === 'ObjectExpression' || (['f$e', 'p$e'].includes(node.left.name) && node.right.type === 'BinaryExpression'))) { if (constants.has(node.left.name)) throw new Error(`Duplicate panel constant ${node.left.name}`) constants.set(node.left.name, node.right) } for (const [key, value] of Object.entries(node)) { if (key === 'loc' || key === 'extra') continue if (Array.isArray(value)) value.forEach(walk) else if (value && typeof value === 'object') walk(value) } } walk(ast) for (const name of Object.keys(definitions)) if (!functions.has(name)) throw new Error(`Missing source function ${name}`) for (const name of constantNames) if (!constants.has(name)) throw new Error(`Missing source constant ${name}`) const cssDirectory = resolve('.tmp/rea-css-recovered') const manifest = JSON.parse(await readFile(resolve(cssDirectory, 'manifest.json'), 'utf8')) if (manifest.sourceEvidenceId !== cssEvidenceId) throw new Error('Fixed CSS Evidence changed') async function stylesheet(file, expected) { const content = await readFile(resolve(cssDirectory, file), 'utf8') if (manifest.files.find(item => item.file === file)?.sha256 !== expected || hash(content) !== expected) throw new Error(`Fixed ${file} SHA-256 mismatch`) return postcss.parse(content, { from: file }) } const motion = await stylesheet(motionFile, motionSha256) const tokens = await stylesheet(tokenFile, tokenSha256) const tokenNames = ['--wb-motion-duration-medium', '--wb-motion-duration-fast', '--wb-motion-easing-decelerate', '--wb-motion-easing-accelerate'] const tokenValues = new Map() tokens.walkDecls(decl => { if (tokenNames.includes(decl.prop) && decl.parent.selectors?.includes(':root')) tokenValues.set(decl.prop, decl.value) }) for (const name of tokenNames) if (!tokenValues.has(name)) throw new Error(`Missing motion token ${name}`) const outputCss = postcss.root() outputCss.append(postcss.rule({ selector: '.source-detail-layout', nodes: tokenNames.map(name => postcss.decl({ prop: name, value: tokenValues.get(name) })) })) let rules = 0 const selected = selector => /\.detail-panel-motion-[\w-]+|\.detail-panel-container(?:[\s.:#[]|$)/.test(selector) function scope(selector) { if (selector.startsWith('.detail-panel-motion-')) return `.source-detail-layout${selector}` return `.source-detail-layout ${selector}` } function project(node) { if (node.type === 'rule') { const selectors = node.selectors.filter(selected) if (!selectors.length) return null rules++ return node.clone({ selector: selectors.map(scope).join(',') }) } if (node.type === 'atrule' && node.nodes && !/keyframes$/.test(node.name)) { const children = node.nodes.map(project).filter(Boolean) return children.length ? node.clone({ nodes: children }) : null } return null } for (const node of motion.nodes) { const projected = project(node); if (projected) outputCss.append(projected) } const bodies = Object.fromEntries([...functions].map(([name, fn]) => [name, { offset: fn.start, bodySha256: hash(source.slice(fn.body.start, fn.body.end)) }])) const identities = { version: '5.8.2', runtimeEvidenceId, runtimeFile, runtimeSha256, cssEvidenceId, motionFile, motionSha256, tokenFile, tokenSha256, functions: bodies, constants: Object.fromEntries([...constants].map(([name, node]) => [name, hash(source.slice(node.start, node.end))])) } const interfaces = `export interface PanelAvailableWidthInput { containerWidth: number; sidebarWidth: number; leadingInset?: number } export interface DetailDrawerWidthInput extends PanelAvailableWidthInput { isMobile?: boolean; preferredWidth?: number } export interface DetailPanelLayoutInput extends PanelAvailableWidthInput { requestedDetailWidth: number } export interface DetailPanelLayoutWidths { mainWidth: number; detailWidth: number } export interface LeadingInsetRebalanceInput { previousInset: number; nextInset: number; isNarrowForDetail: boolean; isPanelVisible: boolean; rememberedDetailWidth: number | null; chatAreaWidth: number } export interface NarrowLayoutState { isMobile: boolean; isNarrowForDetail: boolean; isNarrowForSidebar: boolean; screenWidth: number } ` const constantText = constantNames.map(name => `export const ${name} = ${source.slice(constants.get(name).start, constants.get(name).end)}${constants.get(name).type === 'ObjectExpression' ? ' as const' : ''}`).join('\n') const functionText = Object.entries(definitions).map(([name, spec]) => { const fn = functions.get(name) const overload = spec.implementationParameters ? `/** Preserve source JavaScript null-to-number comparisons behind the typed public overload. */\nexport function ${name}(${spec.parameters}): ${spec.result}\n` : '' return `${overload}export function ${name}(${spec.implementationParameters ?? spec.parameters}): ${spec.result} ${source.slice(fn.body.start, fn.body.end)}` }).join('\n\n') const toMilliseconds = value => value.endsWith('ms') ? Number.parseFloat(value) : Number.parseFloat(value) * 1000 const motionConfig = { openMs: toMilliseconds(tokenValues.get('--wb-motion-duration-medium')), closeMs: toMilliseconds(tokenValues.get('--wb-motion-duration-fast')), openEasing: tokenValues.get('--wb-motion-easing-decelerate'), closeEasing: tokenValues.get('--wb-motion-easing-accelerate') } const typescript = `/* Generated by scripts/rea/recover-panel-layout.mjs from fixed REA Evidence. * Source function bodies and constant object expressions are unchanged; only exports/types are added. * The source runtime leading-inset store is replaced by the explicit zero-default adapter below. * Callers with a registered leading inset must pass leadingInset. Inputs otherwise retain source behavior. */\n${interfaces}\nexport const SOURCE_PANEL_LAYOUT_EVIDENCE = ${JSON.stringify(identities, null, 2)} as const\n\n${constantText}\n\nexport const SIDEBAR_LAYOUT = Rw\nexport const CHAT_LAYOUT = zw\nexport const DETAIL_BREAKPOINT = Bw\nexport const DETAIL_PANEL_LAYOUT = Vw\nexport const DETAIL_NARROW_WIDTH = f$e\nexport const SIDEBAR_NARROW_WIDTH = p$e\nexport const DETAIL_PANEL_MOTION = ${JSON.stringify(motionConfig, null, 2)} as const\n\n/** Host adapter: the source getter returns the currently registered inset; local default is zero. */\nfunction getMainContentLeadingInset(): number { return 0 }\n\n${functionText}\n` const css = `/* Generated by scripts/rea/recover-panel-layout.mjs; WorkBuddy 5.8.2. * REA CSS ${cssEvidenceId}; ${motionFile} sha256=${motionSha256}. * Motion tokens ${tokenFile} sha256=${tokenSha256}. * ${rules} selected rules, original bodies/order and all enclosing media conditions preserved. * .source-detail-layout carries motion phase classes on the same element. */\n${outputCss.toString()}\n` postcss.parse(css) const targetDirectory = resolve('src/renderer/src/workbuddy/workbench') for (const [file, contents] of [['source-panel-layout.ts', typescript], ['panel-motion-source.css', css]]) { const target = resolve(targetDirectory, file) if (process.argv.includes('--check')) { if (await readFile(target, 'utf8') !== contents) throw new Error(`${file} differs from the fixed source recovery`) } else await writeFile(target, contents) } console.log(JSON.stringify({ runtimeEvidenceId, cssEvidenceId, functions: functions.size, constants: constants.size, rules, motion: motionConfig, check: process.argv.includes('--check') }))