import { tr, browserLocale } from '../../../src/lib/ui-i18n.ts'; import { createApp, h, reactive } from 'vue'; import '../../../packages/lab-ui/src/runtime-workbench.js'; const COUNTER='docs.lab.counter'; const LAST_COMMAND='docs.lab.lastCommand'; const diagnostics={mounts:0,unmounts:0,listeners:0,cleanups:0}; const history=[]; export function getDiagnostics(){return {...diagnostics};} function eventRow(event) { const date=new Date(event.timestamp); const data=event.data||{}; const description=event.name==='docs:lab:command'?`${event.appName} · ${data.command}`: event.name==='docs:lab:counter'?tr("{0} · valor {1}", [event.appName, data.value]): tr("{0} · {1} · {2} suscripciones", [data.app||event.appName, data.phase, data.listeners??'—']); return {name:event.name,iso:date.toISOString(),time:date.toLocaleTimeString(browserLocale(),{hour12:false}),description}; } export function register(wu) { let app=null; let disconnect=()=>{}; let disposeDom=()=>{}; wu.define('runtime-lab',{ mount(container,{wu:scoped}) { if(app)throw new Error(tr("runtime-lab ya está montado")); diagnostics.mounts+=1; const state=reactive({counter:scoped.store.get(COUNTER)??0,lastCommand:scoped.store.get(LAST_COMMAND)??'',events:[...history],connected:false,status:'',...diagnostics}); let disposers=[]; const sync=()=>Object.assign(state,diagnostics); const receive=event=>{history.unshift(eventRow(event));history.splice(14);state.events=[...history];}; disconnect=()=>{ const pending=disposers;disposers=[]; for(const dispose of pending){dispose();diagnostics.listeners-=1;diagnostics.cleanups+=1;} state.connected=false;sync(); }; // Subscribe to each leaf: policy scopes are explicit and do not imply permission on the parent path. const connectLeaves=()=>{ if(disposers.length)return; try { const track=dispose=>{disposers.push(dispose);diagnostics.listeners+=1;}; track(scoped.store.on(COUNTER,value=>{state.counter=value??0;})); track(scoped.store.on(LAST_COMMAND,value=>{state.lastCommand=value??'';})); for(const name of ['docs:lab:command','docs:lab:counter','docs:lab:lifecycle'])track(scoped.on(name,receive)); state.counter=scoped.store.get(COUNTER)??0;state.lastCommand=scoped.store.get(LAST_COMMAND)??'';state.connected=true;sync(); }catch(error){disconnect();throw error;} }; const handle=event=>{ const action=event.detail.action; try { if(action==='toggle') { if(state.connected){disconnect();state.status=tr("5 suscripciones liberadas. El store sigue disponible.");} else {connectLeaves();state.status=tr("5 suscripciones activas. Estado sincronizado.");} return; } if(action==='clear'){history.length=0;state.events=[];state.status=tr("Inspector vacío.");return;} const current=scoped.store.get(COUNTER)??0; const next=action==='reset'?0:current+(action==='increment'?1:-1); scoped.store.set(COUNTER,next); scoped.emit('docs:lab:counter',{value:next,source:'Vue'}); state.status=state.connected?tr("Cambio compartido con React."):tr("Store actualizado; la observación local sigue pausada."); }catch(error){state.status=tr("No se pudo completar: {0}", [error.message]);} }; connectLeaves(); app=createApp({render:()=>h('wu-docs-runtime-workbench',{model:{...state}})}); // DOM events retain their exact kebab-case name across Vue's adapter. container.addEventListener('wu-runtime-intent',handle); app.mount(container); disposeDom=()=>container.removeEventListener('wu-runtime-intent',handle); scoped.emit('docs:lab:lifecycle',{app:'runtime-lab',phase:'mount',framework:'Vue',listeners:diagnostics.listeners}); }, unmount(_container,{wu:scoped}) { disconnect();disposeDom();app?.unmount();app=null;diagnostics.unmounts+=1; const event={name:'docs:lab:lifecycle',appName:'runtime-lab',timestamp:Date.now(),data:{app:'runtime-lab',phase:'unmount',listeners:diagnostics.listeners}}; history.unshift(eventRow(event));history.splice(14); scoped.emit(event.name,event.data); }, }); }