124 lines
6.5 KiB
JavaScript
124 lines
6.5 KiB
JavaScript
// Recover only retained REA CSS; never access the installed application here.
|
|
// Sources: REA light/dark Evidence for WorkBuddy 5.8.3 Settings -> Agents.
|
|
// The lazy module contains the browser-extension status rules for this page.
|
|
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 = {
|
|
light: 'ev_1cc896e04c0cc9ceab39b0a252f4982c019305609195ad44804ae9b5fb7379a8',
|
|
dark: 'ev_3384aa03af8a1ccdffb8e025dbd4a354636d8d6b0c207abd21e12fc5eb2ae719'
|
|
}
|
|
const expectedModuleSha = 'cbd4b37c6061381943d96ca454176b2f083e49eb8e04c34cb5d31eef3dbcb8f5'
|
|
const expectedTokensSha = '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696'
|
|
const moduleName = 'ExtensionsSettingsPanel-BnsXh3oa.css'
|
|
|
|
const loadModule = async evidenceId => {
|
|
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 => item.file_path.endsWith(moduleName))
|
|
if (!module) throw new Error(`Agent settings module CSS is absent from ${evidenceId}`)
|
|
const moduleSha = sha(module.source.artifact.text)
|
|
if (moduleSha !== expectedModuleSha || module.source.artifact.sha256 !== expectedModuleSha) {
|
|
throw new Error(`Agent settings module CSS changed in ${evidenceId}`)
|
|
}
|
|
return { filePath: module.file_path, css: module.source.artifact.text, sha: moduleSha }
|
|
}
|
|
|
|
const lightModule = await loadModule(evidenceIds.light)
|
|
const darkModule = await loadModule(evidenceIds.dark)
|
|
if (lightModule.filePath !== darkModule.filePath) throw new Error('Agent settings 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 !== expectedTokensSha) 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 = /\.extensions-settings-panel__browser-extension-status(?:[\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(lightModule.css)
|
|
const darkSharedRoot = sharedRoot(darkModule.css)
|
|
const darkSpecificRoot = postcss.root({ nodes: postcss.parse(darkModule.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 agent 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('.extensions-settings-panel', pick(lightTokens))}\n${scope('[data-theme=dark] .extensions-settings-panel', pick(darkTokens))}\n`
|
|
const evidenceList = Object.entries(evidenceIds).map(([name, evidenceId]) => `${name}=${evidenceId}`).join('; ')
|
|
const css = `/* REA ${evidenceList}; ${moduleName} sha256=${lightModule.sha}; tokens sha256=${tokensSha}. */\n${root.toString()}\n${scopedTokens}`
|
|
await fs.writeFile('src/renderer/src/components/source-agents-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} agent settings 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 !== 4) throw new Error(`Expected 4 agent settings module rules, found ${rules.length}`)
|