37 lines
8.7 KiB
JavaScript
37 lines
8.7 KiB
JavaScript
/** Fixed retained REA only; never reads original WorkBuddy paths. */
|
|
import fs from 'node:fs'
|
|
import crypto from 'node:crypto'
|
|
import { parse } from '@babel/parser'
|
|
import postcss from 'postcss'
|
|
const hash=value=>crypto.createHash('sha256').update(value).digest('hex')
|
|
const archive=JSON.parse(fs.readFileSync('.tmp/rea-runtime-scripts.json','utf8'))
|
|
const source=archive.items.find(item=>item.file_path.endsWith('src-DbE1_mgM.js'))?.source.artifact
|
|
const sourceSha='6c6ce63c93854783270e855572797d75e536e3b8d4548410e31fe9d147e1feb4'
|
|
if(!source||source.sha256!==sourceSha||hash(source.text)!==sourceSha)throw Error('REA script identity mismatch')
|
|
const functions=new Map(),assignments=new Map()
|
|
function walk(node){if(!node||typeof node!=='object')return;if(node.type==='FunctionDeclaration')functions.set(node.id.name,node);if(node.type==='AssignmentExpression'&&node.left.type==='Identifier')assignments.set(node.left.name,node);for(const[key,value]of Object.entries(node)){if(key==='loc'||key==='comments')continue;if(Array.isArray(value))value.forEach(walk);else if(value&&typeof value==='object')walk(value)}}walk(parse(source.text,{sourceType:'module'}).program)
|
|
const manifest={version:'5.8.2',rootSha256:'035254bc76310ea46317433e4840602094543ef323d2d5c5aff7c131729de918',evidence:[archive.evidence_id,'ev_7e9604adb87a3d21570eb7a371001e48b3d316ee0388a03a97e2cb564d03aff7'],sources:[{file:'src-DbE1_mgM.js',sha256:sourceSha}],bodies:[],adaptations:['React/JSX singleton and pinned YCode FloatingUI host dependency replace bundled imports; original dependency version is unknown.','Original useInputBoxStore/useInputProvider/useInputBoxSelector are bound to typed controlled host. Non-null silent preparation is owned by the parent and is a no-op here; remove awaits the actual host and rolls back on failure.','The original chip has no click-to-open-picker behavior. No new click behavior is introduced.','Host scopeKey remount prevents stale clear rollback from crossing owners. Optional disabled state blocks removal during host busy/unavailable state.','Tooltip portal and recovered CSS are scoped to this component; original tooltip function/layer and helper bodies are retained.']}
|
|
function body(name){const node=functions.get(name)??assignments.get(name);if(!node)throw Error('Missing source '+name);const raw=source.text.slice(node.start,node.end);manifest.bodies.push({name,start:node.start,end:node.end,sha256:hash(raw)});let result=(node.type==='AssignmentExpression'?'const ':'')+raw.replace(/\(0,[\w$]+\.(jsx|jsxs)\)/g,' JSX.$1')+';';if(name==='ExpertChip'){const anchor='let a=usePrefixCls(),c=useInputBoxStore()';if(!result.includes(anchor))throw Error('Disabled hook boundary changed');result=result.replace(anchor,'let blocked=useActiveExpertDisabled(),a=usePrefixCls(),c=useInputBoxStore()');const clear='onClick:h,"aria-label":';if(!result.includes(clear))throw Error('Disabled button boundary changed');result=result.replace(clear,'onClick:h,disabled:blocked,"aria-label":')}return result}
|
|
let js=`import * as React from 'react';import * as JSX from 'react/jsx-runtime';import {useFloating,offset as D,flip as E,shift as O,arrow as k,autoUpdate as A,useInteractions,useDismiss,useTransitionStyles,FloatingPortal as NativeFloatingPortal} from '@floating-ui/react';
|
|
import {useInputBoxStore,useInputProvider,useInputBoxSelector,useActiveExpertDisabled} from './SourceActiveExpertRuntime';
|
|
const oG=React,Ei=React,mi=React,ci=React,Di=JSX,io={1:12,2:16},kM=8,gi=3,_i=8,li=9990,ui=10,di='default',fi='__ycodeActiveExpertFloatingLayer__',yi=4,bi=1000,xi=.1,Si='data-pointer-press-transparent',wi=new Set(['SVG','IMG','VIDEO','CANVAS','AUDIO']),Oi=new Map();let Ci=null;
|
|
const usePrefixCls=()=>name=>'cr-'+name,clsx=(...values)=>values.flat().filter(Boolean).join(' '),FloatingPortal=({children})=>JSX.jsx(NativeFloatingPortal,{children:JSX.jsx('div',{className:'yc-source-active-expert','data-cr-theme':children?.props?.['data-cr-theme'],children})});
|
|
`
|
|
for(const name of ['getFloatingLayerState','getGroupStack','nextZIndex','useFloatingLayer','TooltipLayer','isPressTransparent','isHoverSuppressedByPress','suppressHoverUntilPointerMove','isElementTruncated','isSubtreeTruncated','normalizeVisibleText','collectVisibleText','hasVisibleTriggerText','hasTruncationStyle','shouldGateOnTruncation','isEmptyContent','resolveTooltipThemeMode','isVisibleFocus','Tooltip$1','LoadingSpinnerIcon','ChipCloseIcon','toNonEmptyString','resolveAvatarFallbackSeed','getAvatarFallbackBackground','isExpertSelectionDisplayable','hasText','isPreparingExpert','resolveExpertChipAvatar','ExpertAvatar','ExpertChip'])js+=body(name)+'\n'
|
|
js+=`if(typeof document!=='undefined'){document.addEventListener('pointerdown',n=>{Ci=isPressTransparent(n.target)?null:{x:n.clientX,y:n.clientY,at:Date.now()}},true);document.addEventListener('pointermove',n=>{if(Ci&&(Math.abs(n.clientX-Ci.x)>yi||Math.abs(n.clientY-Ci.y)>yi))Ci=null},true)}
|
|
export {ExpertChip,ExpertAvatar,resolveAvatarFallbackSeed,getAvatarFallbackBackground,isExpertSelectionDisplayable,isPreparingExpert,resolveExpertChipAvatar};
|
|
`
|
|
const cssManifest=JSON.parse(fs.readFileSync('.tmp/rea-css-recovered/manifest.json','utf8'))
|
|
let css='/* Source ExpertChip/Tooltip declarations recovered from fixed REA CSS; selectors scoped. */\n'
|
|
for(const[file,pin]of Object.entries({'src-CBaV_A3S.css':'2090ed21045c8f8b0fc388d76424c8751bf18fcd065cbbd5d0ef485163a2e36c','src-CyXHA0IC.css':'a5e26b537bb50323aefbf25abddad52e288dd3868598be8d255d70886fe77cfe'})){
|
|
const text=fs.readFileSync('.tmp/rea-css-recovered/'+file,'utf8');if(hash(text)!==pin||cssManifest.files.find(item=>item.file===file)?.sha256!==pin)throw Error('CSS identity mismatch '+file);manifest.sources.push({file,sha256:pin,evidenceId:cssManifest.sourceEvidenceId});const tree=postcss.parse(text)
|
|
tree.walkRules(rule=>{if(file==='src-CBaV_A3S.css'){const declarations=(rule.nodes??[]).filter(node=>node.type==='decl'&&/^--cr-(?:internal-)?(?:avatar-fallback-bg-|color-on-accent|text-primary|text-secondary|tooltip|chip-hover-bg|focus-border)/.test(node.prop));if(!declarations.length)return;const light=rule.selector.includes('data-cr-theme=light');const clone=rule.clone({nodes:declarations.map(node=>node.clone())});clone.selector=light?'.cr-theme[data-cr-theme=light] .yc-source-active-expert,.yc-source-active-expert[data-cr-theme=light],.light .yc-source-active-expert,[data-theme=light] .yc-source-active-expert':'.yc-source-active-expert';css+=clone.toString()+'\n';return}
|
|
if(!/\.cr-(?:chip-|expert-chip-|tooltip(?:__|--|\b))/.test(rule.selector)||/cr-address|model-selector/.test(rule.selector))return;const clone=rule.clone();clone.selector=clone.selectors.map(selector=>'.yc-source-active-expert '+selector).join(',');if(rule.parent.type==='atrule'){const parent=rule.parent.clone({nodes:[]});parent.append(clone);css+=parent.toString()+'\n'}else css+=clone.toString()+'\n'})
|
|
if(file==='src-CyXHA0IC.css')tree.walkAtRules('keyframes',rule=>{if(['cr-expert-chip-spin','cr-input-spin'].includes(rule.params))css+=rule.toString()+'\n'})
|
|
}
|
|
css+='/* Explicit YCode host busy state, separate from original source declarations. */\n.yc-source-active-expert{display:inline-flex;flex-shrink:0}.yc-source-active-expert[data-disabled=true] .cr-chip-base--static .cr-chip-icon--default{display:inline-flex}.yc-source-active-expert[data-disabled=true] .cr-chip-base--static .cr-chip-icon--hover{display:none}.yc-source-active-expert[data-disabled=true] .cr-chip-base--static:hover{background:transparent}\n'
|
|
const dts=`import type {ComponentType} from 'react';import type {ActiveExpert} from './SourceActiveExpert';export const ExpertChip:ComponentType<{compact?:boolean}>;export const ExpertAvatar:ComponentType<{avatarUrl?:string;name?:string;preparing?:boolean}>;export function isExpertSelectionDisplayable(expert:ActiveExpert):boolean;export function isPreparingExpert(expert:ActiveExpert,id?:string):boolean;export function resolveExpertChipAvatar(expert:ActiveExpert):{avatarUrl?:string;seed:string};export function resolveAvatarFallbackSeed(expert:ActiveExpert):string;export function getAvatarFallbackBackground(name?:string):string;\n`
|
|
const outputs=[['src/renderer/src/capabilities/SourceActiveExpertRecovered.mjs',js],['src/renderer/src/capabilities/SourceActiveExpertRecovered.d.mts',dts],['src/renderer/src/capabilities/SourceActiveExpertSource.css',css]]
|
|
manifest.outputs=outputs.map(([file,text])=>({file,bytes:Buffer.byteLength(text),sha256:hash(text)}));outputs.push(['src/renderer/src/capabilities/SourceActiveExpertManifest.json',JSON.stringify(manifest,null,2)+'\n'])
|
|
for(const[file,text]of outputs){if(process.argv.includes('--check')){if(fs.readFileSync(file,'utf8')!==text)throw Error('Recovery drift '+file)}else fs.writeFileSync(file,text)}console.log(JSON.stringify({status:'PASS',functions:manifest.bodies.length,sources:manifest.sources.length,outputs:outputs.length}))
|