Files
ycode/scripts/rea/recover-chat-header-reference.mjs
T

105 lines
7.6 KiB
JavaScript

// Only read pinned REA exports. Do not read the original installation or execute source modules.
import fs from 'node:fs/promises'
import { createHash } from 'node:crypto'
import { parse } from '@babel/parser'
import postcss from 'postcss'
const output = 'src/renderer/src/components'
const evidence = JSON.parse(await fs.readFile('.tmp/rea-runtime-scripts.json', 'utf8'))
if (evidence.evidence_id !== 'ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb') throw Error('REA script Evidence changed')
const specs = [
['src-DPDj7_qD.js', '747e41d215375e1214c366213aea7e077d85421923029c61cbb6e9562be58de0',
{ I_: 'SourceHeaderSearchIcon', Mv: 'SourceHeaderPanelIcon', xx: 'SourceHeaderPromptIcon' }],
['src-0FGg1seX.js', 'f2c6dbae949041b4e2bb456e8a23ae2837f2d7893ac24bf471485f77c7b15178',
{ ShareTaskIcon: 'SourceHeaderShareIcon', 'EditIcon$2': 'SourceHeaderEditIcon' }],
['contexts-BMeWSUHk.js', '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b',
{ sWe: 'SourceHeaderSearchUpIcon', cWe: 'SourceHeaderSearchDownIcon', lWe: 'SourceHeaderSearchCloseIcon' }],
]
const definitions = []
const manifest = { scriptEvidenceId: evidence.evidence_id, icons: [], css: [] }
for (const [file, sha, names] of specs) {
const text = evidence.items.find(item => item.file_path.endsWith(file))?.source?.artifact?.text
if (!text || createHash('sha256').update(text).digest('hex') !== sha) throw Error(`Source changed: ${file}`)
const found = new Map()
function visit(node) {
if (!node || typeof node !== 'object') return
if (node.type === 'AssignmentExpression' && Object.hasOwn(names, node.left.name)) found.set(node.left.name, node.right)
for (const value of Object.values(node)) if (Array.isArray(value)) value.forEach(visit); else if (value && typeof value === 'object') visit(value)
}
visit(parse(text, { sourceType: 'module' }))
for (const [name, exported] of Object.entries(names)) {
const node = found.get(name)
if (!node) throw Error(`Missing ${file}:${name}`)
let value = text.slice(node.start, node.end)
.replace(/\(0,[A-Za-z0-9_$]+\.(jsx|jsxs|forwardRef|useId)\)/g, ' $1')
if (!/\b(?:jsx|jsxs)\(`svg`/.test(value)) throw Error(`Not a pure SVG: ${name}`)
if (['sWe', 'cWe', 'lWe'].includes(name)) value = `()=>${value}`
definitions.push(`export const ${exported} = ${value}`)
manifest.icons.push({ file, sha256: sha, sourceBinding: name, exported, offset: node.start })
}
}
await fs.writeFile(`${output}/SourceChatHeaderIcons.tsx`, `// @ts-nocheck\n// Pure SVG definitions recovered from pinned REA ${evidence.evidence_id}; no original module initialization.\nimport { jsx, jsxs } from 'react/jsx-runtime'\nimport { forwardRef, useId } from 'react'\n${definitions.join('\n')}\n`)
const cssSpecs = [
['src-Bh3YZ35H.css', '88dbdc1cd3f59d0e8b75824512a465a2be714bc7af481d790fe3a9484f85e01a', /\.wb-button(?:\b|__|--)/],
['contexts-BkjwYSZT.css', '50cbd673f7f35fc7f966bdddc6c9f5677fdfe944cb904888d214619fca259380', /workbuddy-topbar|workbuddy-prompt-list|cb-chat-search/],
['src-CBaV_A3S.css', '2090ed21045c8f8b0fc388d76424c8751bf18fcd065cbbd5d0ef485163a2e36c', /cb-chat-search/],
]
const selected = postcss.root()
let count = 0
function scope(selector) {
if (/\.cb-chat-search-hit/.test(selector)) return selector
if (/\.workbuddy-topbar(?=[\s.:#\[>,+~]|$)/.test(selector)) return selector.replace(/\.workbuddy-topbar(?=[\s.:#\[>,+~]|$)/g, '.source-chat-header.workbuddy-topbar')
if (/\.workbuddy-topbar--[\w-]+/.test(selector)) return selector.replace(/\.workbuddy-topbar--[\w-]+/, match => '.source-chat-header' + match)
return `.source-chat-header ${selector}`
}
function select(node, pattern) {
if (node.type === 'rule') {
if (!pattern.test(node.selector)) return null
const copy = node.clone(); copy.selectors = copy.selectors.map(scope); count++; return copy
}
if (node.type !== 'atrule' || /keyframes/.test(node.name)) return null
const copy = node.clone({ nodes: [] })
for (const child of node.nodes ?? []) { const match = select(child, pattern); if (match) copy.append(match) }
return copy.nodes.length ? copy : null
}
for (const [file, sha, pattern] of cssSpecs) {
const text = await fs.readFile(`.tmp/rea-css-recovered/${file}`, 'utf8')
if (createHash('sha256').update(text).digest('hex') !== sha) throw Error(`CSS changed: ${file}`)
const before = count
for (const node of postcss.parse(text).nodes) { const match = select(node, pattern); if (match) selected.append(match) }
manifest.css.push({ file, sha256: sha, rules: count - before })
}
// Include the exact source token closure for an independently renderable header.
const tokenText = await fs.readFile('.tmp/rea-css-recovered/tokens-Df-i-zsO.css', 'utf8')
if (createHash('sha256').update(tokenText).digest('hex') !== '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696') throw Error('Source tokens changed')
const tokenRules = []; postcss.parse(tokenText).walkRules(rule => tokenRules.push(rule))
const needed = new Set()
const collect = text => { for (const match of text.matchAll(/var\((--[\w-]+)/g)) needed.add(match[1]) }
collect(selected.toString())
for (let pass = 0; pass < 10; pass++) for (const rule of tokenRules) rule.walkDecls(d => { if (needed.has(d.prop)) collect(d.value) })
const light = new Map(), dark = new Map(), desktop = new Map()
for (const rule of tokenRules) {
const map = rule.selector.includes('dark') ? dark : rule.selector.includes(':root') ? light : null
if (map) rule.walkDecls(d => { if (needed.has(d.prop)) map.set(d.prop, d.value) })
if (rule.selector.includes('body[data-electron-desktop=true]')) rule.walkDecls(d => { if (needed.has(d.prop)) desktop.set(d.prop, d.value) })
}
const serialize = map => [...map].map(([name, value]) => `${name}:${value}`).join(';')
await fs.writeFile(`${output}/source-chat-header-recovered.css`, `/* REA CSS ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94. Original rule bodies/order/at-rule conditions; selectors scoped to the independent desktop header, except original chat hit markers. Desktop token overrides are copied from body[data-electron-desktop=true]. */\n.source-chat-header{${serialize(new Map([...light, ...desktop]))}}\n:root[data-theme=dark] .source-chat-header,[data-cr-theme=dark] .source-chat-header{${serialize(dark)}}\n${selected.toString()}\n`)
const labels = {}
const zh = evidence.items.find(item => item.file_path.endsWith('zh-cn-Dfzn8myY.js'))?.source?.artifact?.text
function labelsVisit(node) {
if (!node || typeof node !== 'object') return
if (node.type === 'ObjectProperty') {
const key = node.key.value
if (typeof key === 'string' && /^(chat\.search\.|topbar\.userPromptList|panel\.(?:hideProductPanel|showProductPanel)|contextMenu\.shareTask$|common\.(?:loadingMore|showMore)$)/.test(key)) {
if (node.value.type === 'TemplateLiteral' && !node.value.expressions.length) labels[key] = node.value.quasis[0].value.cooked
else if (node.value.type === 'StringLiteral') labels[key] = node.value.value
}
}
for (const value of Object.values(node)) if (Array.isArray(value)) value.forEach(labelsVisit); else if (value && typeof value === 'object') labelsVisit(value)
}
labelsVisit(parse(zh, { sourceType: 'module' }))
await fs.writeFile(`${output}/source-chat-header-reference.ts`, `// Recovered labels from REA ${evidence.evidence_id}.\nexport const sourceChatHeaderLabels: Record<string,string> = ${JSON.stringify(labels, null, 2)}\n`)
await fs.mkdir('.tmp/chat-header-reference', { recursive: true })
await fs.writeFile('.tmp/chat-header-reference/manifest.json', JSON.stringify({ ...manifest, rules: count }, null, 2))
console.log(JSON.stringify({ icons: definitions.length, rules: count, labels: Object.keys(labels).length }))