chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,338 @@
|
||||
// Materialize the focused artifact-preview CSS projection from captured REA evidence.
|
||||
import crypto from 'node:crypto'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import postcss from 'postcss'
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||||
const cssDir = path.join(rootDir, '.tmp/rea-css-recovered')
|
||||
const cssManifestPath = path.join(cssDir, 'manifest.json')
|
||||
const runtimePath = path.join(rootDir, '.tmp/rea-runtime-scripts.json')
|
||||
const outputPath = path.join(rootDir, 'src/renderer/src/workbuddy/artifacts/artifact-source.css')
|
||||
const projectionPath = path.join(cssDir, 'artifact-projection.json')
|
||||
const cssEvidenceId = 'ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94'
|
||||
const asarEvidenceId = 'ev_97959691fb7cabf7c41f948b08332196924fec1fa8cb71e34dbafd65d912e637'
|
||||
const asarRootSha256 = '035254bc76310ea46317433e4840602094543ef323d2d5c5aff7c131729de918'
|
||||
const runtimeEvidenceId = 'ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb'
|
||||
|
||||
const cssSpecs = [
|
||||
{ file: 'default-dark-CB7rPan2.css', sha256: '6f1a5b770eceb591ca2e922d075335b170b59ae4e85b2828c176d9a1a5b479fe', runtimeOnly: true },
|
||||
{ file: 'src-BPUOynUo.css', sha256: '02a85e652dd1be508ab2bcf1b67880c3673d4c47e50b2caea24ac205bfe8f879' },
|
||||
{ file: 'src-CyXHA0IC.css', sha256: 'a5e26b537bb50323aefbf25abddad52e288dd3868598be8d255d70886fe77cfe' },
|
||||
{ file: 'media-preview-Dvr0Ekj9.css', sha256: '36758fadd61577145a3b46dd12bcd966c662ea0cfaa4e62dc3e31ba4bf7f5fb8' },
|
||||
{ file: 'image-preview-component-6U_ZZMG_.css', sha256: '17387c5eefa224be5ebde7400f6985aca3da39899db60adecb800de7868ca11c' },
|
||||
{ file: 'media-BjV-0OHV.css', sha256: '2d7d011d57d6c4266f6a7d4465f7d6d0b9894892fe291bd52c3dfe2e9f4b18ab' },
|
||||
{ file: 'tokens-Df-i-zsO.css', sha256: '13f2bf82c96e5167a7d7098e36950c6c22c475b3337dbb8bb29ad2fe79cde696' }
|
||||
]
|
||||
|
||||
const runtimeSpecs = [
|
||||
{ file: 'src-Cng7axVa.js', sha256: '5ce8649e3bfac10aec1e63afee726ab45abaafc5aa7cc372892e20f3eba18c8b' },
|
||||
{ file: 'media-preview-DMz4TW4q.js', sha256: 'e8c19693ac7d8d06b45b531de405cc23c071fa19ebd5543b483a43dbdfce8111' },
|
||||
{ file: 'image-preview-component-BgOcW3Xe.js', sha256: '6720317ba018215f96a87d966b08c0d0aa74c26d2298d5fd95f71e8ff2e04e59' },
|
||||
{ file: 'media.module-DC8Y6boy.js', sha256: '2ad55767cb3f3f0302d053022346ecd40921199a16c2b31aff071dc81acbad51' }
|
||||
]
|
||||
|
||||
const moduleSelectors = {
|
||||
'media-preview-DMz4TW4q.js': {
|
||||
object: 'Lo',
|
||||
css: 'media-preview-Dvr0Ekj9.css',
|
||||
keys: {
|
||||
mediaPreview: '.artifact-media-preview',
|
||||
mediaPreviewHeader: '.artifact-media-preview__header',
|
||||
mediaPreviewTitle: '.artifact-media-preview__title',
|
||||
mediaPreviewActions: '.artifact-media-preview__actions',
|
||||
mediaPreviewActionsButton: '.artifact-media-preview__button',
|
||||
mediaPreviewActionsButtonActive: '.artifact-media-preview__button--active',
|
||||
mediaPreviewContent: '.artifact-media-preview__content',
|
||||
mediaPreviewContentFullBleed: '.artifact-media-preview__content--full-bleed',
|
||||
mediaPreviewContentSheet: '.artifact-media-preview__content--sheet'
|
||||
}
|
||||
},
|
||||
'media-preview-DMz4TW4q.js#browser': {
|
||||
object: 'Ur',
|
||||
css: 'media-preview-Dvr0Ekj9.css',
|
||||
keys: {
|
||||
browserFrameContainer: '.artifact-html-frame-container',
|
||||
browserFrame: '.artifact-html-frame',
|
||||
browserFrameEmpty: '.artifact-html-frame-empty',
|
||||
browserFrameError: '.artifact-html-frame-error'
|
||||
}
|
||||
},
|
||||
'image-preview-component-BgOcW3Xe.js': {
|
||||
object: 'Y',
|
||||
css: 'image-preview-component-6U_ZZMG_.css',
|
||||
keys: {
|
||||
previewOverlay: '.wb-image-preview',
|
||||
previewOverlayClosing: '.wb-image-preview--closing',
|
||||
previewStage: '.wb-image-preview__stage',
|
||||
previewStageClosing: '.wb-image-preview__stage--closing',
|
||||
previewImage: '.wb-image-preview__image',
|
||||
previewCloseActionArea: '.wb-image-preview__close-area',
|
||||
previewCloseButton: '.wb-image-preview__close'
|
||||
}
|
||||
},
|
||||
'media.module-DC8Y6boy.js': {
|
||||
object: 'A',
|
||||
css: 'media-BjV-0OHV.css',
|
||||
keys: {
|
||||
bottomCenterActionArea: '.wb-image-preview__toolbar',
|
||||
actionAreaContent: '.wb-image-preview__toolbar-content',
|
||||
actionButtonsArea: '.wb-image-preview__toolbar-buttons',
|
||||
topRightActionArea: '.wb-image-preview__close-area',
|
||||
overlayButton: '.wb-image-preview__control',
|
||||
overlayIcon: '.wb-image-preview__control-icon',
|
||||
scalePercentage: '.wb-image-preview__scale',
|
||||
pageControl: '.wb-image-preview__count',
|
||||
previewContainer: '.wb-image-preview__stage',
|
||||
previewInstance: '.wb-image-preview__image'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex')
|
||||
const cssManifest = JSON.parse(await fs.readFile(cssManifestPath, 'utf8'))
|
||||
const runtimeManifest = JSON.parse(await fs.readFile(runtimePath, 'utf8'))
|
||||
if (cssManifest.sourceEvidenceId !== cssEvidenceId) throw new Error(`CSS Evidence changed: expected ${cssEvidenceId}, found ${cssManifest.sourceEvidenceId ?? 'none'}`)
|
||||
if (runtimeManifest.evidence_id !== runtimeEvidenceId) throw new Error(`Runtime Evidence changed: expected ${runtimeEvidenceId}, found ${runtimeManifest.evidence_id ?? 'none'}`)
|
||||
|
||||
const cssSources = new Map()
|
||||
for (const spec of cssSpecs) {
|
||||
const record = cssManifest.files?.find(item => item.file === spec.file)
|
||||
if (!record || record.sha256 !== spec.sha256) throw new Error(`REA manifest SHA mismatch for ${spec.file}`)
|
||||
if (!spec.runtimeOnly && (record.asarComparison?.status !== 'match' || record.asarComparison.evidenceId !== asarEvidenceId || record.asarComparison.rootSha256 !== asarRootSha256)) {
|
||||
throw new Error(`Static REA comparison is not the locked matching artifact for ${spec.file}`)
|
||||
}
|
||||
const bytes = await fs.readFile(path.join(cssDir, spec.file))
|
||||
const actual = digest(bytes)
|
||||
if (actual !== spec.sha256) throw new Error(`REA CSS digest mismatch for ${spec.file}: ${actual}`)
|
||||
cssSources.set(spec.file, { ...spec, root: postcss.parse(bytes.toString('utf8'), { from: spec.file }) })
|
||||
}
|
||||
|
||||
const runtimeSources = new Map()
|
||||
for (const spec of runtimeSpecs) {
|
||||
const item = runtimeManifest.items?.find(candidate => candidate.file_path?.replace(/\\/g, '/').endsWith(`/assets/${spec.file}`))
|
||||
const artifact = item?.source?.artifact
|
||||
if (!item || item.frame_id !== cssManifest.target?.target_id || artifact?.media_type !== 'text/javascript' || artifact.sha256 !== spec.sha256 || digest(Buffer.from(artifact.text, 'utf8')) !== spec.sha256) {
|
||||
throw new Error(`REA runtime script digest mismatch or missing source: ${spec.file}`)
|
||||
}
|
||||
runtimeSources.set(spec.file, artifact.text)
|
||||
}
|
||||
const artifactSource = runtimeSources.get('src-Cng7axVa.js')
|
||||
for (const selector of ['artifact-content', 'artifact-body', 'plan-content', 'overview-content', 'markdown-content', 'diff-code-content']) {
|
||||
if (!artifactSource.includes('className:`' + selector + '`')) throw new Error(`Recovered renderer DOM class is missing: ${selector}`)
|
||||
}
|
||||
|
||||
function moduleMap(script, objectName) {
|
||||
const generated = new Map()
|
||||
for (const match of script.matchAll(/\b([A-Za-z_$][\w$]*)=`([^`]+)`/g)) generated.set(match[1], match[2])
|
||||
const match = new RegExp(`\\b${objectName}\\s*=\\{([^{}]*)\\}`).exec(script)
|
||||
if (!match) throw new Error(`CSS module object ${objectName} was not found`)
|
||||
const result = new Map()
|
||||
const entryPattern = /(?:([A-Za-z_$][\w$]*)|["']([^"']+)["']):([A-Za-z_$][\w$]*|`[^`]*`)/g
|
||||
for (const entry of match[1].matchAll(entryPattern)) {
|
||||
const key = entry[1] ?? entry[2]
|
||||
const value = entry[3]
|
||||
result.set(key, value.startsWith('`') ? value.slice(1, -1) : generated.get(value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const projectedModules = new Map()
|
||||
const classProjection = new Map()
|
||||
for (const [moduleId, spec] of Object.entries(moduleSelectors)) {
|
||||
const [scriptName, role] = moduleId.split('#')
|
||||
const script = runtimeSources.get(scriptName)
|
||||
const objectName = role === 'browser' ? spec.object : spec.object
|
||||
const mapping = moduleMap(script, objectName)
|
||||
const projected = new Map()
|
||||
for (const [key, localSelector] of Object.entries(spec.keys)) {
|
||||
const generatedClass = mapping.get(key)
|
||||
if (!generatedClass) throw new Error(`CSS module key ${scriptName}:${key} is missing`)
|
||||
const selector = `.${generatedClass}`
|
||||
const source = cssSources.get(spec.css)
|
||||
let found = false
|
||||
source.root.walkRules(rule => {
|
||||
if (postcss.list.comma(rule.selector).some(candidate => candidate.includes(selector))) found = true
|
||||
})
|
||||
if (!found) throw new Error(`CSS module selector ${selector} is missing from ${spec.css}`)
|
||||
projected.set(selector, localSelector)
|
||||
classProjection.set(`${scriptName}:${key}`, { sourceSelector: selector, localSelector, css: spec.css })
|
||||
}
|
||||
projectedModules.set(moduleId, projected)
|
||||
}
|
||||
|
||||
const output = postcss.root()
|
||||
const selected = new Map()
|
||||
function appendProjectedRule(rule, projectSelector, source) {
|
||||
const parts = postcss.list.comma(rule.selector)
|
||||
const selectors = parts.flatMap(selector => {
|
||||
const projected = projectSelector(selector)
|
||||
return Array.isArray(projected) ? projected : projected ? [projected] : []
|
||||
})
|
||||
if (!selectors.length) return
|
||||
let clone = rule.clone({ selector: [...new Set(selectors)].join(',') })
|
||||
for (let parent = rule.parent; parent && parent.type !== 'root'; parent = parent.parent) {
|
||||
if (parent.type === 'atrule' && parent.nodes) clone = parent.clone({ nodes: [clone] })
|
||||
}
|
||||
clone.raws.before = '\n'
|
||||
output.append(clone)
|
||||
selected.set(source, (selected.get(source) ?? 0) + 1)
|
||||
}
|
||||
|
||||
function rewriteMappedSelector(selector, mappings) {
|
||||
let rewritten = selector
|
||||
let matched = false
|
||||
const moduleTokens = selector.match(/\._[A-Za-z][\w-]*_[A-Za-z0-9]+_\d+/g) ?? []
|
||||
if (moduleTokens.some(token => !mappings.has(token))) return undefined
|
||||
for (const [sourceSelector, localSelector] of mappings) {
|
||||
if (!rewritten.includes(sourceSelector)) continue
|
||||
rewritten = rewritten.replaceAll(sourceSelector, localSelector)
|
||||
matched = true
|
||||
}
|
||||
if (!matched) return undefined
|
||||
const selectors = [rewritten]
|
||||
if (/(^|\s)(?::root\.(?:dark|cb-dark)|:root\s+\.(?:dark|cb-dark))\s/.test(selector)) {
|
||||
const hostDark = rewritten.replace(/:root\.(?:dark|cb-dark)|:root\s+\.(?:dark|cb-dark)/, ':root[data-theme=dark]')
|
||||
if (hostDark !== rewritten) selectors.push(hostDark)
|
||||
}
|
||||
return selectors
|
||||
}
|
||||
|
||||
const artifactFamilies = /\.(?:artifact-content|artifact-body|plan-content|overview-content|custom-artifact-content|markdown-content|markdown-viewer|code-content|diff-code-content|diff-loading|diff-error)(?=$|[\s.#:[>+~])/
|
||||
const directImages = new Set(['.cr-image-block', '.cr-image-block-fallback'])
|
||||
for (const file of ['src-BPUOynUo.css', 'src-CyXHA0IC.css']) {
|
||||
const source = cssSources.get(file)
|
||||
source.root.walkRules(rule => {
|
||||
const project = selector => {
|
||||
if (file === 'src-BPUOynUo.css' && artifactFamilies.test(selector)) return selector.replace(/:global\(([^)]+)\)/g, '$1').replace(/\.markdown-content(?=$|[^\w-])/g, '.artifact-markdown-content')
|
||||
if (file === 'src-CyXHA0IC.css' && directImages.has(selector)) return selector
|
||||
return undefined
|
||||
}
|
||||
appendProjectedRule(rule, project, file)
|
||||
})
|
||||
}
|
||||
|
||||
for (const [moduleId, mappings] of projectedModules) {
|
||||
const cssFile = moduleSelectors[moduleId].css
|
||||
const source = cssSources.get(cssFile)
|
||||
source.root.walkRules(rule => {
|
||||
appendProjectedRule(rule, selector => rewriteMappedSelector(selector, mappings), `${cssFile}:${moduleId}`)
|
||||
})
|
||||
}
|
||||
|
||||
const workbuddyTokens = new Map([['light', new Map()], ['dark', new Map()]])
|
||||
// Runtime CSS Evidence locks this theme variable; this file has no separate ASAR comparison.
|
||||
cssSources.get('default-dark-CB7rPan2.css').root.walkRules(rule => {
|
||||
const theme = rule.selector.includes('IDE\\ Night') ? 'dark' : rule.selector.includes('IDE\\ Light') ? 'light' : undefined
|
||||
const background = rule.nodes.find(node => node.type === 'decl' && node.prop === '--vscode-diffCodeContent-background')
|
||||
if (!theme || !background) return
|
||||
output.append(postcss.rule({ selector: theme === 'dark' ? ':root[data-theme=dark] .artifact-content' : ':root .artifact-content', nodes: [background.clone()] }))
|
||||
selected.set(`default-dark-CB7rPan2.css:${theme}`, 1)
|
||||
})
|
||||
cssSources.get('tokens-Df-i-zsO.css').root.walkRules(rule => {
|
||||
const selectors = postcss.list.comma(rule.selector)
|
||||
const dark = selectors.some(selector => /\[data-theme=dark\]|\.dark|cb-dark|vscode-dark|IDE\\ Night/.test(selector))
|
||||
const light = selectors.some(selector => selector.includes(':root') || /IDE\\ Light/.test(selector))
|
||||
const variant = dark ? 'dark' : light ? 'light' : undefined
|
||||
if (!variant) return
|
||||
for (const declaration of rule.nodes.filter(node => node.type === 'decl' && node.prop.startsWith('--wb-'))) {
|
||||
workbuddyTokens.get(variant).set(declaration.prop, declaration)
|
||||
}
|
||||
})
|
||||
const requiredTokens = new Set()
|
||||
output.walkDecls(declaration => {
|
||||
for (const match of declaration.value.matchAll(/var\((--wb-[\w-]+)/g)) requiredTokens.add(match[1])
|
||||
})
|
||||
let discoveredToken = true
|
||||
while (discoveredToken) {
|
||||
discoveredToken = false
|
||||
for (const variant of ['light', 'dark']) {
|
||||
const tokens = workbuddyTokens.get(variant)
|
||||
for (const token of [...requiredTokens]) {
|
||||
const declaration = tokens.get(token) ?? workbuddyTokens.get('light').get(token)
|
||||
if (!declaration) continue
|
||||
for (const match of declaration.value.matchAll(/var\((--wb-[\w-]+)/g)) {
|
||||
if (!requiredTokens.has(match[1])) {
|
||||
requiredTokens.add(match[1])
|
||||
discoveredToken = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [variant, selector] of [
|
||||
['light', ':root .artifact-content, :root .artifact-media-preview, :root .wb-image-preview'],
|
||||
['dark', ':root[data-theme=dark] .artifact-content, :root[data-theme=dark] .artifact-media-preview, :root[data-theme=dark] .wb-image-preview']
|
||||
]) {
|
||||
const declarations = [...requiredTokens]
|
||||
.map(name => workbuddyTokens.get(variant).get(name))
|
||||
.filter(Boolean)
|
||||
.map(declaration => declaration.clone())
|
||||
if (!declarations.length) continue
|
||||
output.append(postcss.rule({ selector, nodes: declarations }))
|
||||
selected.set(`tokens-Df-i-zsO.css:${variant}`, declarations.length)
|
||||
}
|
||||
|
||||
output.append(postcss.rule({
|
||||
selector: '.artifact-content, .artifact-media-preview, .wb-image-preview',
|
||||
nodes: [
|
||||
['--cb-bg-secondary', 'var(--yc-bg-secondary)'],
|
||||
['--cb-hover-bg', 'var(--yc-bg-hover)'],
|
||||
['--cb-panel-bg-primary', 'var(--yc-bg-primary)'],
|
||||
['--cr-md-bg-code-block', 'var(--yc-bg-secondary)'],
|
||||
['--cr-md-text-tertiary', 'var(--yc-text-tertiary)'],
|
||||
['--vscode-font-family', 'var(--yc-font-sans)']
|
||||
].map(([prop, value]) => postcss.decl({ prop, value }))
|
||||
}))
|
||||
|
||||
const usedKeyframes = new Set()
|
||||
output.walkDecls(declaration => {
|
||||
if (!/^animation(?:-name)?$/.test(declaration.prop)) return
|
||||
const sourceNames = declaration.value.match(/_[A-Za-z][\w-]*_[A-Za-z0-9]+_\d+/g) ?? []
|
||||
sourceNames.forEach(name => usedKeyframes.add(name))
|
||||
})
|
||||
for (const { file } of cssSpecs) {
|
||||
const source = cssSources.get(file)
|
||||
if (!source) continue
|
||||
source.root.walkAtRules(/^keyframes$/, rule => {
|
||||
if (!usedKeyframes.has(rule.params)) return
|
||||
const clone = rule.clone()
|
||||
clone.raws.before = '\n'
|
||||
output.append(clone)
|
||||
selected.set(`${file}:keyframes`, (selected.get(`${file}:keyframes`) ?? 0) + 1)
|
||||
})
|
||||
}
|
||||
|
||||
if ((selected.get('src-BPUOynUo.css') ?? 0) < 20) throw new Error('Artifact CSS projection did not recover the expected global preview family')
|
||||
if ((selected.get('src-CyXHA0IC.css') ?? 0) < 2) throw new Error('Markdown image styles were not recovered')
|
||||
const cssHeader = `/* Generated by scripts/rea/recover-artifact-styles.mjs from captured REA Evidence.\n * CSS Evidence ${cssEvidenceId}; runtime Evidence ${runtimeEvidenceId}.\n * Original selected declarations and at-rule/theme conditions are preserved.\n * CSS module classes are projected from locked runtime mappings; source selectors are listed in .tmp/rea-css-recovered/artifact-projection.json.\n */\n`
|
||||
const cssText = `${cssHeader}${output.toString()}\n`
|
||||
postcss.parse(cssText, { from: path.relative(rootDir, outputPath) })
|
||||
await fs.writeFile(outputPath, cssText, 'utf8')
|
||||
|
||||
const projection = {
|
||||
cssEvidenceId,
|
||||
asarEvidenceId,
|
||||
asarRootSha256,
|
||||
runtimeEvidenceId,
|
||||
target: cssManifest.target,
|
||||
selected: [...selected].map(([source, rules]) => ({ source, rules })),
|
||||
hostAdapters: {
|
||||
darkTheme: 'Retained source theme selectors and added :root[data-theme=dark] projections for the WorkBuddy color theme.',
|
||||
moduleClasses: 'Locked CSS module selectors are projected to YCode component classes; unneeded dynamic module selectors are omitted.',
|
||||
selector: ':global(.monaco-editor) is projected to .monaco-editor for a native browser stylesheet.',
|
||||
markdownClass: 'Original artifact-only .markdown-content becomes .artifact-markdown-content to avoid colliding with YCode chat Markdown.',
|
||||
hostTokens: ['--cb-bg-secondary', '--cb-hover-bg', '--cb-panel-bg-primary', '--cr-md-bg-code-block', '--cr-md-text-tertiary', '--vscode-font-family']
|
||||
},
|
||||
css: cssSpecs.map(spec => ({
|
||||
...spec,
|
||||
artifactId: cssManifest.files.find(item => item.file === spec.file).asarComparison?.artifactId,
|
||||
observationScope: spec.runtimeOnly ? 'REA runtime CSS only; no independent ASAR comparison' : 'REA runtime CSS and matching ASAR artifact'
|
||||
})),
|
||||
runtime: runtimeSpecs,
|
||||
moduleProjection: Object.fromEntries([...classProjection].map(([key, value]) => [key, value]))
|
||||
}
|
||||
await fs.writeFile(projectionPath, `${JSON.stringify(projection, null, 2)}\n`, 'utf8')
|
||||
console.log(JSON.stringify({ output: path.relative(rootDir, outputPath), cssEvidenceId, runtimeEvidenceId,
|
||||
selected: projection.selected, projectedModuleClasses: classProjection.size }, null, 2))
|
||||
Reference in new issue
Block a user