// Recover only already-retained REA CSS; never access the installed application here. // // Source: REA evidence ev_24ab58532d4ece0d79f35c8c7d4cb14f5611a7405cdf747c4698e6184bea7d99 // (WorkBuddy 5.8.3 设置 → 外观), 模块样式表 appearance-DMmhwsxq.css(原版懒加载,只有进入外观页才请求)。 // 面板 DOM 与几何来自同一批 REA 证据 `.tmp/todo11-wb-appearance/layout.json`: // .appearance-panel [232,40,638,640]、.appearance-preview 634×317、卡片网格 5 列 118.8×101.2、卡片 118.8×79.2。 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 evidenceId = 'ev_24ab58532d4ece0d79f35c8c7d4cb14f5611a7405cdf747c4698e6184bea7d99' 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') const module = evidence.normalized_result.styles.items.find(item => /appearance-DMmhwsxq\.css$/.test(item.file_path)) if (!module) throw new Error('Appearance module CSS is absent from the evidence') const moduleCss = module.source.artifact.text const moduleSha = sha(moduleCss) if (moduleSha !== 'af58cb3134d3e6c474424f6d718daa8f5be0c6e29d40aa5f9f80c14b3ffbbbd2') throw new Error('Appearance module 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') // 原版外观面板自己的 token 定义(--wb-text-strong 等)在 tokens 样式表里,按组件作用域复制一份。 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) } } // 只保留外观面板相关规则;同时把原版的深色选择器(.dark / [data-cb-theme=dark])改写成 YCode 的 [data-theme=dark]。 const darkSelector = /(^|[\s,>+~])\.dark\b|\[data-cb-theme=dark\]/g function project(node) { if (node.type === 'rule') { const selectors = [] for (const selector of node.selectors) { if (!/\.appearance-/.test(selector)) continue const mapped = selector.replace(darkSelector, (match, prefix) => `${prefix ?? ''}[data-theme=dark]`) 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(project).filter(Boolean) return nodes.length ? node.clone({ nodes }) : null } return null } const root = postcss.root({ nodes: postcss.parse(moduleCss).nodes.map(project).filter(Boolean) }) 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 < 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('appearance-panel', pick(lightTokens))}\n:root[data-theme=dark] ${scope('appearance-panel', pick(darkTokens))}\n` const css = `/* REA ${evidenceId}; appearance-DMmhwsxq.css sha256=${moduleSha}; tokens sha256=${tokensSha}. */\n${root.toString()}\n${scopedTokens}` await fs.writeFile('src/renderer/src/components/source-appearance-panel-recovered.css', css) const rules = [] root.walkRules(rule => rules.push(rule.selector)) console.log(`Recovered ${rules.length} appearance rules (${css.length} bytes); scoped ${pick(lightTokens).size} light / ${pick(darkTokens).size} dark tokens`) console.log(rules.slice(0, 10).join('\n')) if (rules.length < 20) throw new Error('Unexpectedly few appearance rules')