// Recover only retained REA CSS; never access the installed application here. // Sources: REA light/dark evidence for WorkBuddy 5.8.3 Settings -> About / Get Help. // Both captures identify HelpFeedbackPanel-CQNpj-cs.css. import fs from 'node:fs/promises' import { createHash } from 'node:crypto' import postcss from 'postcss' const sha = value => createHash('sha256').update(value).digest('hex') const evidenceIds = { aboutLight: 'ev_7e063ae669e788666080ec25be3247d2c75504d89d076f9963a737cf69ccf25c', helpLight: 'ev_4d3e8c9e277578ef06a16aa4aad5b4766c6a55744c65beb3e8a14033648476c9', aboutDark: 'ev_ed6dce11ff79cf06e3e01c6c1b5ae3e3cc11eac10b37327ce12c06d5c3447053', helpDark: 'ev_ea353952860b6b59699c26fc72d40740b9a3770fe9a3a6e76515686f4363abe6' } const expectedModuleShas = { light: 'fc5a287a1f5a4a76ede74ba971ff12f340ae2193e1dbef9c8898a9b7c3d894e7', dark: '19ec85a3c3fc11a581bbcdf8182e4d78a32dcf5d71307d3da1bbaf92177af765' } const loadModule = async (evidenceId, theme) => { const evidence = JSON.parse(await fs.readFile(`.tmp/rea-css-evidence/${evidenceId}.json`, 'utf8')) if (evidence.evidence_id !== evidenceId) throw new Error(`REA identity mismatch: ${evidenceId}`) const module = evidence.normalized_result.styles.items.find(item => /HelpFeedbackPanel-CQNpj-cs\.css$/.test(item.file_path)) if (!module) throw new Error(`About/help panel module CSS is absent from ${evidenceId}`) const moduleSha = sha(module.source.artifact.text) if (moduleSha !== expectedModuleShas[theme]) throw new Error(`About/help ${theme} module CSS changed in ${evidenceId}`) return { filePath: module.file_path, css: module.source.artifact.text, sha: moduleSha } } const aboutLight = await loadModule(evidenceIds.aboutLight, 'light') const helpLight = await loadModule(evidenceIds.helpLight, 'light') const aboutDark = await loadModule(evidenceIds.aboutDark, 'dark') const helpDark = await loadModule(evidenceIds.helpDark, 'dark') if ([helpLight, aboutDark, helpDark].some(item => item.filePath !== aboutLight.filePath)) throw new Error('About/help panel module path changed across captures') const tokens = await fs.readFile('.tmp/rea-css-recovered/tokens-Df-i-zsO.css', 'utf8') const tokensSha = sha(tokens) if (tokensSha !== '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696') throw new Error('Tokens CSS changed') const lightTokens = new Map() const darkTokens = new Map() for (const rule of postcss.parse(tokens).nodes) { if (rule.type !== 'rule') continue const selectors = rule.selectors ?? [] const dark = selectors.some(selector => selector.includes('[data-theme=dark]')) const light = selectors.some(selector => selector.trim() === ':root') if (!dark && !light) continue for (const node of rule.nodes ?? []) { if (node.type !== 'decl' || !node.prop.startsWith('--')) continue if (dark) darkTokens.set(node.prop, node.value) if (light) lightTokens.set(node.prop, node.value) } } const relatedSelector = /\.help-feedback-panel(?:[\w-]*)?(?![\w-])/i const darkScope = /^\s*(?:\[data-theme=["']?dark["']?\]|body\[data-vscode-theme-name=["']IDE Night["']\]|body\.vscode-dark|\.vscode-dark|body\.dark|\.dark)(?=\s|$)/i function project(node, mode = 'all') { if (node.type === 'rule') { const selectors = [] for (const selector of node.selectors) { if (!relatedSelector.test(selector)) continue const isDark = darkScope.test(selector) if (mode === 'shared' && isDark) continue if (mode === 'dark' && !isDark) continue const mapped = isDark ? selector.replace(darkScope, '[data-theme=dark]') : selector if (!selectors.includes(mapped)) selectors.push(mapped) } return selectors.length ? node.clone({ selector: selectors.join(',') }) : null } if (node.type === 'atrule' && node.nodes) { const nodes = node.nodes.map(child => project(child, mode)).filter(Boolean) return nodes.length ? node.clone({ nodes }) : null } return null } const sharedRoot = css => postcss.root({ nodes: postcss.parse(css).nodes.map(node => project(node, 'shared')).filter(Boolean) }) const lightRoot = sharedRoot(aboutLight.css) const darkSharedRoot = sharedRoot(aboutDark.css) const darkSpecificRoot = postcss.root({ nodes: postcss.parse(aboutDark.css).nodes.map(node => project(node, 'dark')).filter(Boolean) }) const normalizeRules = root => { const rules = [] root.walkRules(rule => { const selectors = rule.selectors.map(selector => selector.replace(/\s+/g, ' ').trim()).join(',') const declarations = (rule.nodes ?? []) .filter(node => node.type === 'decl') .map(node => `${node.prop}:${node.value.replace(/\b0px\b/g, '0').replace(/\s+/g, ' ').trim()}`) .join(';') rules.push(`${selectors}{${declarations}}`) }) return rules.join('\n') } if (normalizeRules(lightRoot) !== normalizeRules(darkSharedRoot)) throw new Error('Light/dark about-help module rules differ beyond serialization') const root = postcss.root({ nodes: [...lightRoot.nodes, ...darkSpecificRoot.nodes] }) const referenced = new Set() const collect = text => { for (const match of text.matchAll(/var\(\s*(--[\w-]+)/g)) referenced.add(match[1]) } collect(root.toString()) for (let depth = 0; depth < 8; depth++) { const before = referenced.size for (const name of [...referenced]) { for (const value of [lightTokens.get(name), darkTokens.get(name)]) { if (typeof value === 'string') collect(value) } } if (referenced.size === before) break } const missingLight = [...referenced].filter(name => !lightTokens.has(name)) if (missingLight.length) throw new Error(`Tokens missing from light root scope: ${missingLight.join(', ')}`) const pick = map => new Map([...referenced].map(name => [name, map.get(name) ?? lightTokens.get(name)])) const scope = (selector, map) => `${selector}{${[...map].map(([property, value]) => `${property}:${value}`).join(';')}}` const scopedTokens = `${scope('.help-feedback-panel', pick(lightTokens))}\n${scope('[data-theme=dark] .help-feedback-panel', pick(darkTokens))}\n` const evidenceList = Object.entries(evidenceIds).map(([name, evidenceId]) => `${name}=${evidenceId}`).join('; ') const css = `/* REA ${evidenceList}; HelpFeedbackPanel-CQNpj-cs.css light-sha256=${aboutLight.sha} dark-sha256=${aboutDark.sha}; tokens sha256=${tokensSha}. */\n${root.toString()}\n${scopedTokens}` await fs.writeFile('src/renderer/src/components/source-about-help-panels-recovered.css', css) const rules = [] root.walkRules(rule => rules.push(rule.selector)) const darkRules = [] darkSpecificRoot.walkRules(rule => darkRules.push(rule.selector)) console.log(`Recovered ${rules.length} about/help rules (${Buffer.byteLength(css)} bytes); ${darkRules.length} dark-specific module rules; scoped ${pick(lightTokens).size} light / ${pick(darkTokens).size} dark tokens`) console.log(rules.join('\n')) if (rules.length < 15) throw new Error('Unexpectedly few about/help rules')