Files
ycode/scripts/rea/recover-general-settings.mjs
T

112 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Recover only already-retained REA CSS; never access the installed application here.
//
// Source: REA evidence ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94
// (WorkBuddy 5.8.x settings window), projected through the retained contexts stylesheet
// `.tmp/rea-css-recovered/contexts-BkjwYSZT.css`. The general settings panel DOM classes were
// observed in REA evidence ev_2309de12a003fca2c863a223cbda5bccad36b3af73e29bc309fdaed94745cf9e
// (settings window layout: `general-settings-panel`, `font-size-slider`, `wb-storage`,
// `power-save-blocker-settings`, `proxy-section`).
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 sourceEvidenceId = 'ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94'
const layoutEvidenceId = 'ev_2309de12a003fca2c863a223cbda5bccad36b3af73e29bc309fdaed94745cf9e'
const contexts = await fs.readFile('.tmp/rea-css-recovered/contexts-BkjwYSZT.css', 'utf8')
const contextsSha = sha(contexts)
if (contextsSha !== '50cbd673f7f35fc7f966bdddc6c9f5677fdfe944cb904888d214619fca259380') throw new Error('Contexts CSS changed')
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')
// UI kit:设置窗口实际加载的控件样式表(按钮 / 输入框 / 开关 / 图标)。
const kit = await fs.readFile('.tmp/rea-css-recovered/src-Bh3YZ35H.css', 'utf8')
const kitSha = sha(kit)
if (kitSha !== '88dbdc1cd3f59d0e8b75824512a465a2be714bc7af481d790fe3a9484f85e01a') throw new Error('UI kit CSS changed')
// 选择器白名单:通用设置面板本体、字号滑块、存储卡片、电源与代理相关规则。
const panelled = /\.(general-settings-panel|font-size-slider|wb-storage|power-save-blocker-settings|proxy-section)(?![a-zA-Z0-9])/
function project(node) {
if (node.type === 'rule') {
const selectors = node.selectors.filter(selector => panelled.test(selector))
return selectors.length ? node.clone({ selector: selectors.join(',') }) : null
}
if (node.type === 'atrule' && node.nodes) {
const nodes = node.nodes.map(project).filter(Boolean)
return nodes.length ? node.clone({ nodes }) : null
}
return null
}
const parsed = postcss.parse(contexts)
const root = postcss.root({ nodes: parsed.nodes.map(project).filter(Boolean) })
// 原版的 .wb-button / .wb-input / .wb-switch / .wb-icon 来自全局 UI kit;YCode 里同样收敛到面板作用域。
// 用 :where() 前缀保持原版层叠:UI kit 规则与面板规则同权重,面板规则在后(原版面板 chunk 后加载),
// 例如 .wb-storage__open-folder{height:auto} 必须仍能覆盖 .wb-button--medium{height:32px}。
const kitScoped = /^\.wb-(button|input|switch|icon)(?![a-zA-Z])/
function projectKit(node) {
if (node.type === 'rule') {
const selectors = node.selectors.filter(selector => kitScoped.test(selector.trim()))
return selectors.length ? node.clone({ selector: selectors.map(selector => `:where(.general-settings-panel) ${selector}`).join(',') }) : null
}
if (node.type === 'atrule' && node.nodes) {
const nodes = node.nodes.map(projectKit).filter(Boolean)
return nodes.length ? node.clone({ nodes }) : null
}
return null
}
const kitRoot = postcss.root({ nodes: postcss.parse(kit).nodes.map(projectKit).filter(Boolean) })
// 原版把 `--wb-*` 设计变量放在全局 :root(tokens 样式表),YCode 的既有做法是按组件作用域
// 复制一份,避免全局污染。这里同样把 tokens 的 :root / 深色作用域合并成通用面板作用域。
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)
}
}
// UI kit 的控件 token 定义在它自己的样式表里。
for (const rule of postcss.parse(kit).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)
}
}
if (lightTokens.size < 200 || darkTokens.size < 200) throw new Error('Tokens CSS lacks root scopes')
// 只输出面板规则真正引用到的变量(含 var() 传递闭包),避免把整套全局 token 复制进面板作用域。
const referenced = new Set()
const collect = (text) => { for (const match of text.matchAll(/var\(\s*(--[\w-]+)/g)) referenced.add(match[1]) }
collect(root.toString())
collect(kitRoot.toString())
for (let depth = 0; depth < 6; depth++) {
const before = referenced.size
for (const name of [...referenced]) {
const value = lightTokens.get(name) ?? darkTokens.get(name)
if (typeof value === 'string') collect(value)
}
if (referenced.size === before) break
}
const missing = [...referenced].filter(name => !lightTokens.has(name))
if (missing.length) console.log(`Tokens missing from light root scope: ${missing.join(', ')}`)
const pick = (map) => new Map([...referenced].filter(name => map.has(name)).map(name => [name, map.get(name)]))
const scope = (name, map) => `.${name}{${[...map].map(([property, value]) => `${property}:${value}`).join(';')}}`
const scopedTokens = `${scope('general-settings-panel', pick(lightTokens))}\n:root[data-theme=dark] ${scope('general-settings-panel', pick(darkTokens))}\n`
const css = `/* REA ${sourceEvidenceId} via contexts sha256=${contextsSha}; tokens sha256=${tokensSha}; ui-kit sha256=${kitSha}; general settings panel classes from REA ${layoutEvidenceId}. */\n${kitRoot.toString()}\n${root.toString()}\n${scopedTokens}`
await fs.writeFile('src/renderer/src/components/source-general-settings-recovered.css', css)
const rules = []
root.walkRules(rule => rules.push(rule.selector))
console.log(`Recovered ${rules.length} general-settings rules (${css.length} bytes)`)
console.log(rules.slice(0, 12).join('\n'))
if (rules.length < 60) throw new Error('Unexpectedly few general settings rules')
console.log(`Scoped ${pick(lightTokens).size} light / ${pick(darkTokens).size} dark referenced design tokens into .general-settings-panel`)