2026-09-05 09:41:44 +00:00
import { test } from 'node:test' ;
import assert from 'node:assert/strict' ;
import fs from 'node:fs' ;
import vm from 'node:vm' ;
import ts from '../apps/web/node_modules/typescript/lib/typescript.js' ;
2026-09-06 05:39:06 +00:00
function loadCatalog ( file ) {
2026-09-08 01:14:53 +00:00
const source = fs . readFileSync ( file , 'utf8' ) ;
// transpileModule prints output even for a catalog that does not parse, so a
// missing comma silently loads a shorter catalog and every key check below
// still passes. Ask the parser, which actually keeps its errors.
const parsed = ts . createSourceFile ( file , source , ts . ScriptTarget . ESNext , true , ts . ScriptKind . TS ) ;
assert . deepEqual ( ( parsed . parseDiagnostics || [ ] ) . map ( d => ts . flattenDiagnosticMessageText ( d . messageText , ' ' ) ) , [ ] , ` ${ file } must parse ` ) ;
const code = ts . transpileModule ( source , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
2026-09-06 05:39:06 +00:00
const sandbox = { exports : { } , require : ( ) => ( { } ) } ;
vm . runInNewContext ( code , sandbox ) ;
return sandbox . exports . zhTW || sandbox . exports . en ;
}
const zhTW = loadCatalog ( 'apps/web/src/locales/zh-TW.ts' ) ;
const en = loadCatalog ( 'apps/web/src/locales/en.ts' ) ;
const catalogs = { 'zh-TW' : zhTW , en } ;
const i18n = { locale : 'en' , t : ( key , params ) => { const message = catalogs [ i18n . locale ] ? . [ key ] ? ? key ; return params ? message . replace ( /\{(\w+)\}/g , ( m , name ) => Object . hasOwn ( params , name ) ? String ( params [ name ] ) : m ) : message } , getLocale : ( ) => i18n . locale } ;
2026-09-05 09:41:44 +00:00
const raw = fs . readFileSync ( 'apps/web/src/schedule.tsx' , 'utf8' ) ;
const js = ts . transpileModule ( raw , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . React } } ) . outputText ;
2026-09-06 05:39:06 +00:00
const box = { exports : { } , require : ( ) => i18n } ; vm . runInNewContext ( js , box ) ; const { cronFromPreset , presetFromCron , defaultCronPreset , describeCron , describeCronHuman } = box . exports ;
2026-09-05 09:41:44 +00:00
test ( 'fixed intervals retain exact elapsed units' , ( ) => {
for ( const unit of [ 'minutes' , 'hours' , 'days' ] ) { const p = { ... defaultCronPreset ( ) , freq : 'Interval' , n : 45 , unit } ; const cron = cronFromPreset ( p ) ; const restored = presetFromCron ( cron ) ; assert . equal ( restored . n , 45 ) ; assert . equal ( restored . unit , unit ) ; }
} ) ;
test ( 'unknown cron is preserved verbatim instead of silently rewritten' , ( ) => {
for ( const cron of [ '0 0 9 * * 1' , '0 25 * * *' , '99 9 * * *' , '0 0 */3 * *' , '*/45 * * * *' ] ) { const p = presetFromCron ( cron ) ; assert . equal ( p . freq , 'Advanced' ) ; assert . equal ( cronFromPreset ( p ) , cron ) ; }
} ) ;
test ( 'blank advanced expressions are never converted to a scheduled job' , ( ) => assert . equal ( cronFromPreset ( { ... defaultCronPreset ( ) , freq : 'Advanced' , cron : '' } ) , '' ) ) ;
test ( 'VNC paste delegates once to confirmed backend and rejects another source' , async ( ) => {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const handlers = { } , sent = [ ] ; class RFB { addEventListener ( ) { } focus ( ) { } sendKey ( ) { } }
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage : x => sent . push ( x ) } , addEventListener : ( n , f ) => handlers [ n ] = f } ;
vm . runInNewContext ( source , { window , document : { location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : ( ) => ( { } ) , querySelector : ( ) => null } , navigator : { clipboard : { readText : async ( ) => '中文\nhello' } } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
await handlers . keydown ( { ctrlKey : true , code : 'KeyV' , preventDefault ( ) { } , stopImmediatePropagation ( ) { } } ) ;
assert . equal ( sent . filter ( x => x . type === 'lazyboy-paste-text' ) . length , 1 ) ;
assert . equal ( sent . at ( - 1 ) . text , '中文\nhello' ) ;
handlers . message ( { origin : 'http://localhost' , source : { } , data : { type : 'lazyboy-host-clipboard' , text : 'bad' } } ) ; assert . equal ( sent . at ( - 1 ) . text , '中文\nhello' ) ;
} ) ;
2026-09-08 13:27:38 +00:00
2026-09-05 14:59:07 +00:00
const mdJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/markdown.tsx' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . ReactJSX } } ) . outputText ;
const mdBox = { exports : { } , require : ( name ) => {
if ( name === 'react' ) return { useCallback : fn => fn , useRef : ( ) => ( { current : null } ) , useState : ( ) => [ false , ( ) => { } ] , memo : fn => fn } ;
if ( name === 'react/jsx-runtime' ) return { jsx : ( ) => null , jsxs : ( ) => null , Fragment : 'Fragment' } ;
if ( name === 'react-markdown' || name === 'remark-gfm' || name === 'remark-breaks' ) return { default : ( ) => null } ;
if ( name === './i18n' ) return { t : key => key } ;
if ( name . endsWith ( '.css' ) ) return { } ;
throw new Error ( 'unexpected import ' + name ) ;
} } ;
vm . runInNewContext ( mdJs , mdBox ) ;
const { sanitizeMarkdownUrl } = mdBox . exports ;
2026-09-06 03:43:38 +00:00
const audioJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/call-audio.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const audioBox = { exports : { } , require : ( ) => { throw new Error ( 'unexpected import' ) } } ;
vm . runInNewContext ( audioJs , audioBox ) ;
2026-09-06 05:39:06 +00:00
const { floatToPcm16 , pcm16ToFloat , resample , VOICE _SAMPLE _RATE , CAPTURE _CHUNK _SAMPLES } = audioBox . exports ;
function fakePlaybackContext ( rendered ) {
return {
sampleRate : VOICE _SAMPLE _RATE , currentTime : 0 ,
createBuffer : ( _channels , length , rate ) => { const data = new Float32Array ( length ) ; rendered . push ( data ) ; return { length , duration : length / rate , getChannelData : ( ) => data } } ,
createBufferSource : ( ) => ( { buffer : null , connect ( ) { } , start ( ) { } , stop ( ) { } } ) ,
} ;
}
2026-09-06 03:43:38 +00:00
test ( 'pcm16 roundtrip keeps amplitude sign and resample identity at 24 kHz' , ( ) => {
const source = new Float32Array ( [ 0 , 0.5 , - 0.5 , 1 , - 1 ] ) ;
const bytes = floatToPcm16 ( source ) ;
const back = pcm16ToFloat ( bytes . buffer ) ;
assert . equal ( back . length , source . length ) ;
assert . ok ( back [ 1 ] > 0.49 && back [ 1 ] < 0.51 ) ;
assert . ok ( back [ 2 ] < - 0.49 && back [ 2 ] > - 0.51 ) ;
const same = resample ( source , VOICE _SAMPLE _RATE , VOICE _SAMPLE _RATE ) ;
assert . equal ( same , source ) ;
const down = resample ( new Float32Array ( [ 0 , 1 , 0 , 1 ] ) , 48000 , 24000 ) ;
assert . equal ( down . length , 2 ) ;
} ) ;
2026-09-05 14:59:07 +00:00
test ( 'markdown links only keep http(s), mailto, tel, and in-page hashes' , ( ) => {
assert . equal ( sanitizeMarkdownUrl ( 'https://example.com/docs' ) , 'https://example.com/docs' ) ;
assert . equal ( sanitizeMarkdownUrl ( 'mailto:hi@example.com' ) , 'mailto:hi@example.com' ) ;
assert . equal ( sanitizeMarkdownUrl ( '#section' ) , '#section' ) ;
assert . equal ( sanitizeMarkdownUrl ( 'javascript:alert(1)' ) , undefined ) ;
assert . equal ( sanitizeMarkdownUrl ( 'data:text/html,<script>alert(1)</script>' ) , undefined ) ;
assert . equal ( sanitizeMarkdownUrl ( '/relative' ) , undefined ) ;
} ) ;
2026-09-06 03:43:38 +00:00
2026-09-06 05:39:06 +00:00
test ( 'zh-TW and en catalogs share keys and placeholders' , ( ) => {
const zhKeys = Object . keys ( zhTW ) . sort ( ) ;
const enKeys = Object . keys ( en ) . sort ( ) ;
assert . deepEqual ( enKeys , zhKeys ) ;
for ( const key of zhKeys ) {
const slots = s => [ ... String ( s ) . matchAll ( /\{(\w+)\}/g ) ] . map ( m => m [ 1 ] ) . sort ( ) ;
assert . deepEqual ( slots ( en [ key ] ) , slots ( zhTW [ key ] ) , key ) ;
assert . notEqual ( String ( en [ key ] ) . trim ( ) , '' ) ;
assert . notEqual ( String ( zhTW [ key ] ) . trim ( ) , '' ) ;
}
} ) ;
test ( 'describeCron and stored Chinese human strings follow the UI locale' , ( ) => {
i18n . locale = 'en' ;
assert . equal ( describeCron ( '0 9 * * *' ) , 'Daily at 9:00 AM' ) ;
assert . equal ( describeCron ( '0 9 * * 1-5' ) , 'Weekdays at 9:00 AM' ) ;
assert . equal ( describeCronHuman ( '每天 09:00' ) , 'Daily at 9:00 AM' ) ;
assert . equal ( describeCronHuman ( '每隔 45 分鐘(固定間隔)' ) , 'Every 45 minutes (elapsed)' ) ;
i18n . locale = 'zh-TW' ;
assert . equal ( describeCron ( '0 9 * * *' ) , '每天 09:00' ) ;
assert . equal ( describeCronHuman ( 'Daily at 9:00 AM' ) , '每天 09:00' ) ;
} ) ;
2026-09-06 03:43:38 +00:00
test ( 'hanging up before microphone permission resolves releases the late stream' , async ( ) => {
let grant ; let stopped = 0 ;
const context = { exports : { } , navigator : { mediaDevices : { getUserMedia : ( ) => new Promise ( resolve => grant = resolve ) } } } ;
vm . runInNewContext ( audioJs , context ) ;
const audio = new context . exports . CallAudio ( { onCapture ( ) { } , onError ( ) { } } ) ;
const starting = audio . start ( ) ;
audio . stop ( ) ;
grant ( { getTracks : ( ) => [ { stop ( ) { stopped ++ ; } } ] } ) ;
await starting ;
assert . equal ( stopped , 1 ) ;
} ) ;
2026-09-06 05:39:06 +00:00
test ( 'odd-sized audio frames stay sample-aligned instead of turning into noise' , ( ) => {
const rendered = [ ] ;
const audio = new audioBox . exports . CallAudio ( { onCapture ( ) { } , onError ( ) { } } ) ;
audio . context = fakePlaybackContext ( rendered ) ;
const source = new Int16Array ( [ 1000 , - 1000 , 2000 , - 2000 , 3000 , - 3000 , 4000 , - 4000 ] ) ;
const bytes = new Uint8Array ( source . buffer ) ;
let offset = 0 ;
for ( const size of [ 3 , 5 , 7 , 1 ] ) { audio . play ( bytes . slice ( offset , offset + size ) . buffer ) ; offset += size }
const played = rendered . flatMap ( chunk => [ ... chunk ] ) ;
const expected = [ ... pcm16ToFloat ( bytes . slice ( ) . buffer ) ] ;
assert . equal ( played . length , expected . length ) ;
expected . forEach ( ( value , index ) => assert . ok ( Math . abs ( played [ index ] - value ) < 1e-6 , ` sample ${ index } ` ) ) ;
} ) ;
test ( 'microphone frames are batched instead of sent one websocket message per block' , ( ) => {
const sent = [ ] ;
const audio = new audioBox . exports . CallAudio ( { onCapture : pcm => sent . push ( pcm . length ) , onError ( ) { } } ) ;
const block = new Float32Array ( 64 ) ;
const blocksPerChunk = CAPTURE _CHUNK _SAMPLES / block . length ;
for ( let i = 0 ; i < blocksPerChunk - 1 ; i ++ ) audio . queueCapture ( block ) ;
assert . deepEqual ( sent , [ ] ) ;
audio . queueCapture ( block ) ;
assert . deepEqual ( sent , [ CAPTURE _CHUNK _SAMPLES * 2 ] ) ;
} ) ;
2026-09-06 14:09:17 +00:00
function mobileViewer ( ) {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const handlers = { } , options = { } , sent = [ ] ;
const keyboard = { value : '' , focused : false , focus ( ) { this . focused = true } , blur ( ) { this . focused = false } , setSelectionRange ( ) { } } ;
const pointerEvents = [ ] ;
const canvas = { style : { } , closest : selector => selector === '#screen' ? { } : null , getBoundingClientRect : ( ) => ( { left : 0 , top : 0 , width : 300 , height : 200 } ) } ;
const elements = { } ;
const element = id => elements [ id ] || ( elements [ id ] = { style : { } , attributes : { } , setAttribute ( k , v ) { this . attributes [ k ] = v } , querySelectorAll ( ) { return [ ] } } ) ;
let rfb ;
class RFB { constructor ( ) { rfb = this } addEventListener ( ) { } focus ( ) { keyboard . focused = false }
_handleMouseMove ( x , y ) { pointerEvents . push ( { type : 'move' , x , y } ) }
_handleMouseButton ( x , y , mask ) { pointerEvents . push ( { type : 'button' , x , y , mask } ) }
_handleWheel ( event ) { pointerEvents . push ( { type : 'wheel' , dx : event . deltaX , dy : event . deltaY } ) }
}
2026-09-07 09:05:14 +00:00
const body = { className : '' , classList : { tokens : new Set ( ) , toggle ( name , force ) { if ( force === undefined ) force = ! this . tokens . has ( name ) ; if ( force ) this . tokens . add ( name ) ; else this . tokens . delete ( name ) ; body . className = [ ... this . tokens ] . join ( ' ' ) } } } ;
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage : x => sent . push ( x ) } , addEventListener : ( n , f , o ) => { handlers [ n ] = f ; options [ n ] = o } , matchMedia : ( ) => ( { matches : false , addEventListener ( ) { } } ) } ;
vm . runInNewContext ( source , { window , document : { body , location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : id => id === 'mobile-keyboard' ? keyboard : element ( id ) , querySelector : selector => selector === '#screen canvas' ? canvas : null } , navigator : { } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
return { handlers , options , sent , keyboard , canvas , rfb , window , pointerEvents , elements , body } ;
2026-09-06 14:09:17 +00:00
return { handlers , options , sent , keyboard , canvas , rfb , window , pointerEvents , elements } ;
}
2026-09-07 09:05:14 +00:00
test ( 'direct mobile taps position without unexpectedly opening the keyboard' , ( ) => {
2026-09-06 14:09:17 +00:00
const { handlers : h , options , keyboard , canvas , rfb } = mobileViewer ( ) ;
for ( const name of [ 'touchstart' , 'touchmove' , 'touchend' ] ) assert . equal ( options [ name ] . capture , true ) ;
const point = { clientX : 20 , clientY : 30 } ;
h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchend ( { touches : [ ] } ) ;
2026-09-07 09:05:14 +00:00
assert . equal ( keyboard . focused , false ) ; assert . equal ( rfb . focusOnClick , false ) ;
2026-09-06 14:09:17 +00:00
keyboard . blur ( ) ; h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchmove ( { touches : [ { clientX : 50 , clientY : 30 } ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
h . touchstart ( { target : canvas , touches : [ point , point ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
rfb . viewOnly = true ; h . touchstart ( { target : canvas , touches : [ point ] } ) ; h . touchend ( { touches : [ ] } ) ; assert . equal ( keyboard . focused , false ) ;
} ) ;
test ( 'mobile IME sends committed Chinese once and supports input-only backspace and Enter' , ( ) => {
const { handlers : h , keyboard , sent } = mobileViewer ( ) ;
sent . length = 0 ;
h . compositionstart ( { target : keyboard } ) ; keyboard . value += '中文' ;
h . input ( { target : keyboard , isComposing : true } ) ; assert . equal ( sent . length , 0 ) ;
h . compositionend ( { target : keyboard } ) ; h . input ( { target : keyboard , isComposing : false } ) ;
assert . equal ( sent . length , 1 ) ; assert . equal ( sent [ 0 ] . text , '中文' ) ;
keyboard . value = '' ; h . input ( { target : keyboard , inputType : 'deleteContentBackward' } ) ;
let prevented = false ; h . beforeinput ( { target : keyboard , inputType : 'insertLineBreak' , preventDefault ( ) { prevented = true } } ) ;
assert . equal ( prevented , true ) ;
assert . deepEqual ( sent . map ( x => x . text || x . key ) , [ '中文' , 'BackSpace' , 'Return' ] ) ;
} ) ;
test ( 'mobile keyboard is cleared and blurred when control is released; foreign messages cannot change control' , ( ) => {
const { handlers : h , keyboard , sent , window } = mobileViewer ( ) ;
keyboard . focus ( ) ; keyboard . value += 'secret' ;
h . message ( { origin : window . location . origin , source : { } , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( keyboard . focused , true ) ;
h . message ( { origin : window . location . origin , source : window . parent , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( keyboard . focused , false ) ; assert . equal ( keyboard . value , '\u200b' ) ;
sent . length = 0 ; keyboard . value += 'blocked' ; h . input ( { target : keyboard } ) ; assert . equal ( sent . length , 0 ) ;
} ) ;
function pointerButton ( viewer , action ) {
viewer . handlers . click ( { target : { closest : ( ) => ( { dataset : { action } } ) } } ) ;
}
function pointerTouch ( viewer , name , points ) {
let prevented = false , stopped = false ;
2026-09-07 09:05:14 +00:00
viewer . handlers [ name ] ( { target : { closest : selector => selector === '#trackpad' ? { } : null } , touches : points . map ( ( [ clientX , clientY ] ) => ( { clientX , clientY } ) ) , preventDefault ( ) { prevented = true } , stopImmediatePropagation ( ) { stopped = true } } ) ;
2026-09-06 14:09:17 +00:00
return prevented && stopped ;
}
test ( 'trackpad moves relative to cursor, clamps edges and taps without opening keyboard' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
assert . equal ( pointerTouch ( v , 'touchstart' , [ [ 30 , 30 ] ] ) , true ) ;
pointerTouch ( v , 'touchmove' , [ [ 60 , 40 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . length , 1 ) ; assert . equal ( v . pointerEvents [ 0 ] . x , 180 ) ; assert . ok ( Math . abs ( v . pointerEvents [ 0 ] . y - 110 ) < . 001 ) ;
assert . equal ( v . keyboard . focused , false ) ;
pointerTouch ( v , 'touchstart' , [ [ 10 , 10 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents . slice ( - 2 ) . map ( e => e . mask ) , [ 1 , 0 ] ) ;
pointerTouch ( v , 'touchstart' , [ [ 10 , 10 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 1000 , - 1000 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . x , 299 ) ; assert . equal ( v . pointerEvents . at ( - 1 ) . y , 0 ) ;
pointerButton ( v , 'keyboard' ) ; assert . equal ( v . keyboard . focused , true ) ;
} ) ;
test ( 'two-finger trackpad scroll and right tap do not also left click' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] , [ 50 , 20 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 20 , 80 ] , [ 50 , 80 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents , [ { type : 'wheel' , dx : - 0 , dy : - 60 } ] ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] , [ 50 , 20 ] ] ) ; pointerTouch ( v , 'touchend' , [ [ 20 , 20 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . deepEqual ( v . pointerEvents . slice ( - 2 ) . map ( e => e . mask ) , [ 4 , 0 ] ) ;
} ) ;
test ( 'drag is released on cancellation, mode switch, blur and control handoff' , ( ) => {
for ( const release of [ 'cancel' , 'mode' , 'blur' , 'handoff' ] ) {
const v = mobileViewer ( ) ; pointerButton ( v , 'drag' ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . mask , 1 ) ;
if ( release === 'cancel' ) v . handlers . touchcancel ( ) ;
if ( release === 'mode' ) pointerButton ( v , 'mode' ) ;
if ( release === 'blur' ) v . handlers . blur ( ) ;
if ( release === 'handoff' ) v . handlers . message ( { origin : v . window . location . origin , source : v . window . parent , data : { type : 'lazyboy-view-only' , viewOnly : true } } ) ;
assert . equal ( v . pointerEvents . at ( - 1 ) . mask , 0 , release ) ;
assert . equal ( v . elements [ 'pointer-drag' ] . attributes [ 'aria-pressed' ] , 'false' ) ;
}
} ) ;
test ( 'view-only pointer controls never send mouse input and trackpad asks for control' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ; v . rfb . viewOnly = true ;
for ( const action of [ 'left' , 'right' , 'drag' , 'up' , 'down' ] ) pointerButton ( v , action ) ;
pointerTouch ( v , 'touchstart' , [ [ 20 , 20 ] ] ) ; pointerTouch ( v , 'touchmove' , [ [ 50 , 50 ] ] ) ; pointerTouch ( v , 'touchend' , [ ] ) ;
assert . equal ( v . pointerEvents . length , 0 ) ;
assert . equal ( v . sent . at ( - 1 ) . type , 'lazyboy-request-control' ) ;
} ) ;
2026-09-07 09:05:14 +00:00
test ( 'trackpad mode swallows screen taps so fingers on the picture do not click' , ( ) => {
const v = mobileViewer ( ) ; pointerButton ( v , 'mode' ) ;
let prevented = false , stopped = false ;
v . handlers . touchstart ( { target : v . canvas , touches : [ { clientX : 20 , clientY : 30 } ] , preventDefault ( ) { prevented = true } , stopImmediatePropagation ( ) { stopped = true } } ) ;
assert . equal ( prevented && stopped , true ) ;
assert . equal ( v . pointerEvents . length , 0 ) ;
assert . equal ( v . keyboard . focused , false ) ;
} ) ;
test ( 'coarse pointers start in trackpad mode with a body class for independent controls' , ( ) => {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const body = { className : '' , classList : { tokens : new Set ( ) , toggle ( name , force ) { if ( force ) this . tokens . add ( name ) ; else this . tokens . delete ( name ) ; body . className = [ ... this . tokens ] . join ( ' ' ) } } } ;
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage ( ) { } } , addEventListener ( ) { } , matchMedia : ( ) => ( { matches : true , addEventListener ( ) { } } ) } ;
const elements = { } ; const element = id => elements [ id ] || ( elements [ id ] = { style : { } , attributes : { } , hidden : true , setAttribute ( k , v ) { this . attributes [ k ] = v } , querySelectorAll ( ) { return [ ] } } ) ;
class RFB { constructor ( ) { } addEventListener ( ) { } focus ( ) { } }
vm . runInNewContext ( source , { window , document : { body , location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : id => element ( id ) , querySelector : ( ) => null } , navigator : { } , RFB , setTimeout ( ) { } , clearTimeout ( ) { } } ) ;
assert . equal ( body . className , 'touch-ui' ) ;
assert . equal ( elements . trackpad . hidden , false ) ;
} ) ;
test ( 'mobile shortcut row sends modifiers and hides with the keyboard' , ( ) => {
const v = mobileViewer ( ) ;
pointerButton ( v , 'keyboard' ) ;
assert . equal ( v . keyboard . focused , true ) ;
assert . equal ( v . elements [ 'keyboard-shortcuts' ] . hidden , false ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'modifier' , key : 'ctrl' } } ) } } ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'key' , key : 'c' } } ) } } ) ;
assert . equal ( v . sent . at ( - 1 ) . type , 'lazyboy-mobile-key' ) ;
assert . equal ( v . sent . at ( - 1 ) . key , 'ctrl+c' ) ;
v . handlers . click ( { target : { closest : ( ) => ( { dataset : { action : 'hide-keyboard' } } ) } } ) ;
assert . equal ( v . keyboard . focused , false ) ;
assert . equal ( v . elements [ 'keyboard-shortcuts' ] . hidden , true ) ;
} ) ;
2026-09-08 01:14:53 +00:00
test ( 'status sits outside the remote pixels; screenshots stay opt-in' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . doesNotMatch ( app , /RunStatus/ ) ;
2026-09-07 13:08:10 +00:00
assert . match ( app , /<RunProbe runId=\{computer\.busyRunId\|\|computer\.waitingRunId\}><Avatar/ ) ;
2026-09-08 01:14:53 +00:00
assert . match ( app , /--visible-height/ ) ;
2026-09-07 13:08:10 +00:00
assert . match ( app , /<div className=\{`composer-dock \$\{statusMembers\.length\?"has-status":""\}`\}>\s*\{error&&<div className="error-banner"/ ) ;
2026-09-07 09:05:14 +00:00
const css = fs . readFileSync ( 'apps/web/src/computer.css' , 'utf8' ) ;
2026-09-07 13:08:10 +00:00
assert . doesNotMatch ( css , /\.run-status/ ) ;
2026-09-08 01:14:53 +00:00
const chat = fs . readFileSync ( 'apps/web/src/chat.css' , 'utf8' ) ;
2026-09-07 13:08:10 +00:00
assert . match ( chat , /\.composer-dock\{position:relative/ ) ;
assert . doesNotMatch ( chat , /\.composer-dock\{position:absolute/ ) ;
assert . match ( chat , /\.messages\{min-width:0;padding-bottom:24px/ ) ;
2026-09-07 09:05:14 +00:00
const tools = fs . readFileSync ( 'crates/api/src/tools.rs' , 'utf8' ) ;
assert . match ( tools , /screenshots are opt-in with observe:true/ ) ;
assert . match ( tools , /if args\.get\("observe"\)\.and_then\(Value::as_bool\) != Some\(true\)/ ) ;
const runs = fs . readFileSync ( 'crates/api/src/runs.rs' , 'utf8' ) ;
assert . match ( runs , /Never silently stop/ ) ;
assert . match ( runs , /let goal_mode = goal_mode \|\| !chat_only/ ) ;
} ) ;
2026-09-07 08:27:33 +00:00
const monitorJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/run-monitor.tsx' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS , jsx : ts . JsxEmit . ReactJSX } } ) . outputText ;
const monitorBox = { exports : { } , require : name => {
if ( name === 'react' ) return { useCallback : fn => fn , useEffect : ( ) => { } , useRef : ( ) => ( { current : null } ) , useState : ( ) => [ null , ( ) => { } ] } ;
if ( name === 'react/jsx-runtime' ) return { jsx : ( ) => null , jsxs : ( ) => null , Fragment : 'Fragment' } ;
if ( name === './api' ) return { api : async ( ) => ( { activity : [ ] } ) } ;
if ( name === './i18n' ) return { t : i18n . t , getLocale : ( ) => i18n . locale } ;
throw new Error ( 'unexpected import ' + name ) ;
} } ;
vm . runInNewContext ( monitorJs , monitorBox ) ;
const { formatElapsed , shortDuration , errorActions , errorTitle , trailText } = monitorBox . exports ;
const FAILURE _CODES = [ 'interrupted' , 'tool_timeout' , 'model_key' , 'model_quota' , 'model_unknown' , 'model_timeout' , 'network' , 'computer_gone' , 'lease_lost' , 'unknown' ] ;
test ( 'run timings stay on one glanceable line' , ( ) => {
assert . equal ( formatElapsed ( 0 ) , '0:00' ) ;
assert . equal ( formatElapsed ( 9_500 ) , '0:09' ) ;
assert . equal ( formatElapsed ( 65_000 ) , '1:05' ) ;
assert . equal ( formatElapsed ( 3_725_000 ) , '1:02:05' ) ;
assert . equal ( formatElapsed ( - 5 ) , '0:00' ) ;
assert . equal ( formatElapsed ( NaN ) , '0:00' ) ;
assert . equal ( shortDuration ( 340 ) , '340ms' ) ;
assert . equal ( shortDuration ( 6_400 ) , '6.4s' ) ;
assert . equal ( shortDuration ( 65_000 ) , '1:05' ) ;
assert . equal ( shortDuration ( null ) , '' ) ;
assert . equal ( shortDuration ( - 1 ) , '' ) ;
} ) ;
test ( 'every failure code offers at least one action, in the right order' , ( ) => {
const buttons = code => errorActions ( code ) . join ( ',' ) ;
assert . equal ( buttons ( 'model_key' ) , 'settings,retry' ) ;
assert . equal ( buttons ( 'model_unknown' ) , 'settings,retry' ) ;
assert . equal ( buttons ( 'model_quota' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'model_timeout' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'network' ) , 'retry,settings' ) ;
assert . equal ( buttons ( 'computer_gone' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'tool_timeout' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'interrupted' ) , 'screen,retry' ) ;
assert . equal ( buttons ( 'lease_lost' ) , 'retry' ) ;
for ( const code of [ ... FAILURE _CODES , 'made_up' , undefined , null ] ) {
const actions = errorActions ( code ) ;
assert . ok ( actions . length > 0 , String ( code ) ) ;
assert . ok ( actions . every ( action => [ 'retry' , 'screen' , 'settings' ] . includes ( action ) ) , code ) ;
assert . equal ( new Set ( actions ) . size , actions . length , 'no duplicate buttons for ' + code ) ;
}
} ) ;
test ( 'failure titles resolve in both catalogs and never repeat themselves' , ( ) => {
for ( const locale of Object . keys ( catalogs ) ) {
i18n . locale = locale ;
const titles = FAILURE _CODES . map ( code => errorTitle ( code ) ) ;
for ( const [ index , title ] of titles . entries ( ) ) {
assert . notEqual ( title , title . startsWith ( 'errorTitle' ) ? title : ` ${ title } -missing ` , FAILURE _CODES [ index ] ) ;
assert . ok ( ! title . startsWith ( 'errorTitle' ) , ` ${ locale } ${ FAILURE _CODES [ index ] } has no copy ` ) ;
assert . ok ( title . trim ( ) . length > 0 ) ;
}
assert . equal ( new Set ( titles ) . size , titles . length , locale ) ;
assert . ok ( errorTitle ( 'made_up' ) . length > 0 ) ;
}
i18n . locale = 'en' ;
} ) ;
test ( 'trail lines read as sentences with the detail a stuck run needs' , ( ) => {
assert . equal ( trailText ( { id : 1 , kind : 'model' , createdAt : '' , turn : 3 , elapsedMs : 6400 , text : '先打開網頁' } ) , 'turn 3, thought for 6.4s — 先打開網頁' ) ;
assert . match ( trailText ( { id : 2 , kind : 'tool' , createdAt : '' , step : 'browser: click #12' , status : 'ok' , elapsedMs : 400 } ) , /browser: click #12 · ok · 400ms/ ) ;
assert . match ( trailText ( { id : 3 , kind : 'tool' , createdAt : '' , step : 'shell: npm test' , status : 'timed_out' , snippet : 'killed after 150s' } ) , /timed out — killed after 150s/ ) ;
assert . match ( trailText ( { id : 4 , kind : 'run' , createdAt : '' , event : 'started' , task : '整理下載資料' } ) , /整理下載資料/ ) ;
assert . match ( trailText ( { id : 5 , kind : 'run' , createdAt : '' , event : 'completed' , turns : 9 } ) , /Done in 9 turns/ ) ;
assert . match ( trailText ( { id : 6 , kind : 'run' , createdAt : '' , event : 'waiting_input' , reason : '登入' } ) , /Waiting for you: 登入/ ) ;
2026-09-07 09:05:14 +00:00
assert . match ( trailText ( { id : 10 , kind : 'run' , createdAt : '' , event : 'paused' , reason : '已達本輪執行上限' } ) , /Paused: 已達本輪執行上限/ ) ;
2026-09-07 08:27:33 +00:00
assert . match ( trailText ( { id : 7 , kind : 'run' , createdAt : '' , event : 'retry' } ) , /Re-queued/ ) ;
assert . match ( trailText ( { id : 8 , kind : 'retry' , createdAt : '' , attempt : 2 , gaveUp : true , error : '429 rate limit' } ) , /attempt 2 failed, retrying · gave up — 429 rate limit/ ) ;
assert . match ( trailText ( { id : 9 , kind : 'notice' , createdAt : '' , text : '這輪不需要電腦' } ) , /這輪不需要電腦/ ) ;
} ) ;
test ( 'the bubble reads the run activity endpoint the API actually mounts' , ( ) => {
const monitor = fs . readFileSync ( 'crates/api/src/monitor.rs' , 'utf8' ) ;
assert . match ( monitor , /\.route\("\/api\/runs\/\{id\}\/activity", get\(activity\)\)/ ) ;
assert . match ( monitor , /\.route\("\/api\/runs\/\{id\}\/retry", post\(retry\)\)/ ) ;
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . match ( app , /chip\.kind==="error"\?/ ) ;
assert . match ( app , /errorActions\(chip\.code\)/ ) ;
assert . match ( app , /`\/api\/runs\/\$\{runId\}\/retry`,\{method:"POST",body:"\{\}"\}/ ) ;
assert . match ( app , /<RunProbe runId=\{chip\.runId\|\|null\} align="end" label=\{t\("errorDetails"\)\}/ ) ;
assert . match ( app , /<RunProbe runId=\{member\.id===computer\.botId\?computer\.busyRunId:null\}><Avatar/ ) ;
const probe = fs . readFileSync ( 'apps/web/src/run-monitor.tsx' , 'utf8' ) ;
assert . match ( probe , /`\/api\/runs\/\$\{runId\}\/activity\$\{after\}`/ ) ;
2026-09-07 13:08:10 +00:00
assert . doesNotMatch ( probe , /export function RunStatus/ ) ;
2026-09-07 08:27:33 +00:00
assert . match ( fs . readFileSync ( 'apps/web/src/main.tsx' , 'utf8' ) , /import "\.\/monitor\.css";/ ) ;
} ) ;
2026-09-07 13:08:10 +00:00
const liveJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/live.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const liveBox = { exports : { } } ; vm . runInNewContext ( liveJs , liveBox ) ;
const { sessionEventsUrl , subscribeToSession , SESSION _EVENT _TYPES , createCoalescer } = liveBox . exports ;
test ( 'the browser listens for every event kind the api can append' , ( ) => {
const rust = [ 'crates/api/src/runs.rs' , 'crates/api/src/sessions.rs' , 'crates/api/src/schedules.rs' , 'crates/api/src/voice_call.rs' ] . map ( file => fs . readFileSync ( file , 'utf8' ) ) . join ( '\n' ) ;
const emitted = [ ... rust . matchAll ( /["']((?:message|run|session)\.[a-z_]+)["']/g ) ] . map ( match => match [ 1 ] ) ;
assert . ok ( emitted . length >= 4 , 'the api should emit session events' ) ;
for ( const kind of new Set ( emitted ) ) assert . ok ( SESSION _EVENT _TYPES . includes ( kind ) , ` ${ kind } is emitted but never listened for ` ) ;
} ) ;
test ( 'the live feed reads its own session, drops replays, and survives a bad frame' , ( ) => {
const listeners = { } ; let closed = 0 , url = '' ;
const source = { addEventListener : ( type , listener ) => { listeners [ type ] = listener } , close : ( ) => { closed += 1 } } ;
const seen = [ ] , statuses = [ ] ;
const feed = subscribeToSession ( 's/1' , event => seen . push ( event ) , { source : candidate => { url = candidate ; return source } , onStatus : connected => statuses . push ( connected ) } ) ;
assert . equal ( url , '/api/sessions/s%2F1/events' ) ;
assert . ok ( listeners [ 'message.created' ] && listeners [ 'run.completed' ] && listeners [ 'session.cleared' ] , 'every kind gets a listener' ) ;
listeners [ 'message.created' ] ( { lastEventId : '5' , data : '{"seq":5,"role":"assistant"}' } ) ;
// The event object was built inside a vm realm, so compare fields, not prototypes.
assert . equal ( seen . length , 1 ) ;
assert . equal ( seen [ 0 ] . kind , 'message.created' ) ; assert . equal ( seen [ 0 ] . id , 5 ) ;
assert . equal ( seen [ 0 ] . payload . seq , 5 ) ; assert . equal ( seen [ 0 ] . payload . role , 'assistant' ) ;
listeners [ 'message.created' ] ( { lastEventId : '5' , data : '{}' } ) ;
listeners [ 'message.created' ] ( { lastEventId : '4' , data : '{}' } ) ;
assert . equal ( seen . length , 1 , 'a reconnect replay must not be applied twice' ) ;
listeners [ 'run.started' ] ( { lastEventId : '6' , data : 'not json' } ) ;
assert . equal ( seen . length , 2 ) ; assert . equal ( seen [ 1 ] . kind , 'run.started' ) ; assert . equal ( seen [ 1 ] . id , 6 ) ;
assert . equal ( Object . keys ( seen [ 1 ] . payload ) . length , 0 , 'a malformed frame still reports the event' ) ;
listeners [ 'open' ] ( ) ; listeners [ 'error' ] ( ) ;
assert . deepEqual ( statuses , [ true , false ] ) ;
feed . close ( ) ; feed . close ( ) ;
assert . equal ( closed , 1 , 'close is idempotent' ) ;
assert . equal ( sessionEventsUrl ( 'abc' ) , '/api/sessions/abc/events' ) ;
} ) ;
test ( 'a burst of session events settles into one refresh' , ( ) => {
const timers = [ ] ; let runs = 0 ;
const schedule = ( callback , ms ) => { timers . push ( { callback , ms } ) ; return timers . length - 1 } ;
const dismiss = handle => { if ( timers [ handle ] ) timers [ handle ] . cancelled = true } ;
const coalescer = createCoalescer ( ( ) => { runs += 1 } , 120 , schedule , dismiss ) ;
coalescer . kick ( ) ; coalescer . kick ( ) ; coalescer . kick ( ) ;
assert . equal ( runs , 0 ) ;
assert . equal ( timers . filter ( entry => ! entry . cancelled ) . length , 1 , 'one pending run' ) ;
assert . equal ( timers [ 0 ] . ms , 120 ) ;
timers [ 0 ] . callback ( ) ;
assert . equal ( runs , 1 ) ;
coalescer . kick ( ) ; coalescer . cancel ( ) ; coalescer . cancel ( ) ;
assert . equal ( timers [ 1 ] . cancelled , true ) ;
assert . equal ( runs , 1 , 'a cancelled run never fires' ) ;
coalescer . kick ( ) ; timers [ 2 ] . callback ( ) ;
assert . equal ( runs , 2 , 'the coalescer is reusable after a cancel' ) ;
} ) ;
test ( 'chat follows the event stream instead of a fixed two second poll' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
2026-09-09 06:41:09 +00:00
assert . match ( app , /subscribeToSession\(activeSessionId,event=>\{[^}]*settle\.kick\(\)\}/ , 'every non-streaming event kicks the settle coalescer' ) ;
2026-09-07 13:08:10 +00:00
assert . match ( app , /createCoalescer\(\(\)=>\{if\(!document\.hidden\)refresh\(\)\.catch\(\(\)=>\{\}\)\},EVENT_SETTLE_MS\)/ ) ;
assert . match ( app , /document\.addEventListener\("visibilitychange",resume\)/ ) ;
assert . doesNotMatch ( app , /const timer=setInterval\(\(\)=>\{refresh\(\)/ , 'the 2s transcript poll should be gone' ) ;
assert . match ( app , /const heartbeat=window\.setInterval\(\(\)=>\{const beat=roomsRef\.current[\s\S]*\},HEARTBEAT_MS\)/ , 'the heartbeat keeps its own minute cadence' ) ;
} ) ;
2026-09-08 01:14:53 +00:00
// The screen veil and the frame that survives it: both used to be driven by a
// timer plus "null the url", which is what made booting and handing over feel
// like a stall instead of a gesture.
const handoffJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/handoff.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const handoffBox = { exports : { } , require : ( ) => ( { } ) } ; vm . runInNewContext ( handoffJs , handoffBox ) ;
const { viewerPath , keepScreenUrl , handoffRemaining , nextVeil , viewOnlyFor , HANDOFF _MS , HANDOFF _MIN _MS , VEIL _FADE _MS } = handoffBox . exports ;
test ( 'the viewer path is stable, so a frame can mount before the desktop answers' , ( ) => {
assert . equal ( viewerPath ( 'bot 7' ) , '/view/bot%207/vnc.html' ) ;
assert . equal ( viewerPath ( 'a/b' ) , '/view/a%2Fb/vnc.html' ) ;
assert . equal ( viewerPath ( 'bot' ) , viewerPath ( 'bot' ) , 'no nonce, or the frame would remount every poll' ) ;
} ) ;
test ( 'the path the frame mounts on is the byte the api hands back' , ( ) => {
// The viewer mounts on the client's own path while the desktop is coming up,
// then adopts the url from the status round trip. If the two ever differ the
// iframe remounts mid-boot, which is the black flash this whole path exists
// to avoid, so the invariant is pinned to the server's own format string.
const routes = fs . readFileSync ( 'crates/api/src/routes.rs' , 'utf8' ) ;
const served = ( routes . match ( /"url": format!\("([^"]+)"\)/ ) || [ ] ) [ 1 ] ;
assert . equal ( served , '/view/{id}/vnc.html' ) ;
const id = '6f1d3a0e-2b7c-4d5e-8f90-1a2b3c4d5e6f' ;
assert . equal ( viewerPath ( id ) , served . replace ( '{id}' , id ) ) ;
} ) ;
test ( 'pixels are dropped only for a computer that is really gone' , ( ) => {
const url = '/view/bot/vnc.html' ;
for ( const state of [ 'booting' , 'suspended' , 'running' ] ) assert . equal ( keepScreenUrl ( url , null , state ) , url , state + ' must keep its VNC session' ) ;
for ( const state of [ 'stopped' , 'error' ] ) assert . equal ( keepScreenUrl ( url , null , state ) , null , state + ' has no desktop to keep' ) ;
assert . equal ( keepScreenUrl ( null , null , 'running' ) , null , 'nothing was ever mounted' ) ;
assert . equal ( keepScreenUrl ( url , '/other' , 'booting' ) , '/other' , 'a fresh url still wins' ) ;
assert . equal ( keepScreenUrl ( null , url , 'booting' ) , url , 'the first url arrives while booting' ) ;
} ) ;
test ( 'a handoff holds one visible beat, never the whole round trip' , ( ) => {
assert . ok ( HANDOFF _MIN _MS < HANDOFF _MS , 'the beat needs a floor and a ceiling' ) ;
assert . ok ( VEIL _FADE _MS <= HANDOFF _MIN _MS , 'the fade has to finish inside the beat' ) ;
assert . ok ( HANDOFF _MS <= 1_000 , 'a reply that never lands cannot strand the mascot' ) ;
assert . equal ( handoffRemaining ( 1_000 , 1_000 ) , HANDOFF _MIN _MS , 'a reply this instant still owes the beat' ) ;
assert . equal ( handoffRemaining ( 1_000 , 1_000 + HANDOFF _MIN _MS ) , 0 , 'served' ) ;
assert . equal ( handoffRemaining ( 1_000 , 1_000 + ( HANDOFF _MIN _MS + HANDOFF _MS ) / 2 ) , 0 , 'past the floor is never a wait' ) ;
assert . equal ( handoffRemaining ( 1_000 , 1_000 + HANDOFF _MS ) , 0 ) ;
const midway = handoffRemaining ( 1_000 , 1_000 + HANDOFF _MIN _MS - 10 ) ;
assert . equal ( midway , 10 , 'the remaining beat shrinks with the clock' ) ;
} ) ;
test ( 'the veil fades in with its label and back out through the same mascot' , ( ) => {
const empty = { label : null , leaving : false } ;
assert . equal ( nextVeil ( null , empty ) , empty , 'with nothing to say there is nothing to show' ) ;
const shown = nextVeil ( '換手中' , empty ) ;
assert . equal ( shown . label , '換手中' ) ; assert . equal ( shown . leaving , false ) ;
assert . equal ( nextVeil ( '換手中' , shown ) , shown , 'a repeated status must not restart the animation' ) ;
const leaving = nextVeil ( null , shown ) ;
assert . equal ( leaving . label , '換手中' , 'the label is held while it fades' ) ; assert . equal ( leaving . leaving , true ) ;
assert . equal ( nextVeil ( null , leaving ) , leaving , 'it leaves exactly once' ) ;
const replaced = nextVeil ( '喚醒中' , shown ) ;
assert . equal ( replaced . label , '喚醒中' ) ; assert . equal ( replaced . leaving , false , 'a new status replaces the label' ) ;
const revived = nextVeil ( '喚醒中' , leaving ) ;
assert . equal ( revived . label , '喚醒中' ) ; assert . equal ( revived . leaving , false , 'and pulls it back out of the fade' ) ;
} ) ;
test ( 'only a human moves the mouse, and the veil never holds it back' , ( ) => {
assert . equal ( viewOnlyFor ( 'user' ) , false ) ;
for ( const holder of [ 'none' , 'bot' ] ) assert . equal ( viewOnlyFor ( holder ) , true , holder ) ;
} ) ;
2026-09-08 09:14:15 +00:00
test ( 'shared input stays interactive across agent ownership and pause states' , ( ) => {
for ( const holder of [ 'none' , 'bot' , 'user' ] ) assert . equal ( viewOnlyFor ( holder , true ) , false , holder ) ;
} ) ;
2026-09-08 01:14:53 +00:00
test ( 'taking the screen flips the mouse on the click, not on the reply' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
const body = app . slice ( app . indexOf ( 'async function setControl(' ) ) ;
const flip = body . indexOf ( 'controlHolder:holder' ) ;
2026-09-08 09:14:15 +00:00
const gate = body . indexOf ( 'pushViewOnly(viewOnlyFor(holder,computer.sharedInput))' ) ;
2026-09-08 01:14:53 +00:00
const post = body . indexOf ( 'await api(`/api/computer/${botId}/${holder==="user"?"takeover":"release"}`' ) ;
const readBack = body . indexOf ( 'finally{' ) ;
assert . ok ( flip > 0 && gate > 0 && post > 0 && readBack > 0 , 'optimistic flip, gate, request, and read back all present' ) ;
assert . ok ( flip < post && gate < post , 'badge and input move before the server answers' ) ;
assert . ok ( readBack > post , 'the truth is read back after the request' ) ;
assert . match ( body . slice ( readBack ) , /await refresh\(\)/ ) ;
assert . match ( body , /if\(!botId\|\|controlBusyRef\.current\)return/ , 'a double click cannot fight itself' ) ;
2026-09-08 09:14:15 +00:00
assert . match ( app , /if\(computer\.sharedInput\)\{pushViewOnly\(!desktopInteractive\);return\}void setControl\("user"\)/ ) ;
2026-09-08 01:14:53 +00:00
assert . match ( app , /if\(expectedHolderRef\.current&&status\.controlHolder===expectedHolderRef\.current\)/ , 'agreement, not a timer, ends the handoff' ) ;
assert . match ( app , /onClick=\{onTakeOver\}/ ) ;
assert . match ( app , /onClick=\{onRelease\}/ ) ;
assert . doesNotMatch ( app , /action\(\(\)=>api\(`\/api\/computer\/[^`]*takeover/ , 'takeover no longer rides the global busy path' ) ;
assert . equal ( ( app . match ( /\/api\/computer\/\$\{[^}]*\}\/(takeover|release)/g ) || [ ] ) . length , 0 , 'every handoff goes through setControl' ) ;
assert . equal ( ( app . match ( /holder==="user"\?"takeover":"release"/g ) || [ ] ) . length , 1 , 'one request path, one owner of it' ) ;
2026-09-08 09:14:15 +00:00
} ) ;
test ( 'shared CUA desktops hide takeover and keep the mouse live' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . match ( app , /sharedInput:true/ , 'the first paint already assumes shared input' ) ;
assert . match ( app , /if\(!computer\.sharedInput\)await setControl\("user",id\)/ , 'opening the login screen must not pause a shared run' ) ;
assert . match ( app , /if\(active&&!computer\.sharedInput\)void setControl\("user",active\.id\)/ , 'a call only takes exclusive control on older exclusive desktops' ) ;
assert . match ( app , /computer\.sharedInput\?\(computer\.takeoverRequested\|\|computer\.controlHolder==="user"\?<button className="primary" disabled=\{busy\|\|pending\} onClick=\{onRelease\}>\{t\("doneContinue"\)\}<\/button>:null\)/ ) ;
assert . match ( app , /computer\.sharedInput\?t\("sharedDesktop"\)/ ) ;
assert . match ( app , /computer\.sharedInput\?t\("sharedNeedsUser"/ ) ;
assert . match ( app , /takeover=\{computer\.takeoverRequested\|\|\(!computer\.sharedInput&&computer\.controlHolder==="user"\)\}/ ) ;
const vnc = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) ;
assert . match ( vnc , /rfb\.viewOnly = true;/ , 'a dropped RFB is muted locally' ) ;
assert . doesNotMatch ( vnc , /disconnect[\s\S]{0,400}applyViewOnly\(true\)/ , 'disconnect must not overwrite host intent' ) ;
2026-09-08 01:14:53 +00:00
} ) ;
test ( 'the waiting veil covers the desktop the way it always did' , ( ) => {
const app = fs . readFileSync ( 'apps/web/src/App.tsx' , 'utf8' ) ;
assert . match ( app , /<div className="preview">\{computerOpen\?<EmptyComputer state=\{computer\.state\}\/>:frame\}\{!computerOpen&&hud\}<\/div>/ ) ;
assert . match ( app , /<div className="overlay-desktop">\{frame\}\{hud\}<\/div>/ ) ;
assert . match ( app , /const hud=paneBot&&veil\.label\?<ComputerHud bot=\{paneBot\} label=\{veil\.label\} leaving=\{veil\.leaving\}\/>:null/ ) ;
const css = fs . readFileSync ( 'apps/web/src/computer.css' , 'utf8' ) ;
// Both parents are what make inset:0 cover the desktop rather than the page.
assert . match ( css , /\.computer-part \.preview\{position:relative/ ) ;
assert . match ( css , /\.overlay-screen \.overlay-desktop\{position:relative/ ) ;
// These values were compared against main in a real browser: rendered pixel
// for pixel identical, so drift here means the veil visibly changed.
const hud = css . match ( /^\.computer-hud\{([^}]*)\}/m ) [ 1 ] ;
for ( const rule of [ 'position:absolute' , 'inset:0' , 'z-index:3' , 'display:grid' , 'align-content:center' , 'justify-items:center' , 'gap:14px' , 'padding:10px 12px 8px' , 'border-radius:inherit' , 'background:radial-gradient(ellipse at center,#142722e8,#101012ed)' , 'backdrop-filter:blur(8px)' , 'pointer-events:none' ] ) assert . ok ( hud . includes ( rule ) , ` the veil needs ${ rule } ` ) ;
assert . match ( css , /\.computer-hud\.is-leaving\{opacity:0\}/ ) ;
assert . match ( css , /\.computer-hud\{[^}]*transition:opacity \.22s ease\}/ , 'it fades instead of blinking off' ) ;
// main drew the label as static mint text; the shimmer belongs to the chat
// working label, and the veil keeps the mascot as the only moving part.
const label = css . match ( /^\.computer-hud-label\{([^}]*)\}/m ) [ 1 ] ;
assert . ok ( ! label . includes ( 'animation' ) , 'the veil label does not animate' ) ;
assert . ok ( label . includes ( 'color:#c9ddd5' ) && label . includes ( 'font-size:12px' ) && label . includes ( 'text-align:center' ) && label . includes ( 'max-width:90%' ) ) ;
assert . ok ( ! /\.computer-hud[^{]*\{[^}]*working-shimmer/ . test ( css ) , 'nothing shimmers inside the veil' ) ;
assert . match ( css , /\.computer-signal\{[^}]*width:64px[^}]*animation:monitor-breathe/ ) ;
assert . match ( css , /\.computer-signal::before\{[^}]*monitor-orbit/ ) ;
const responsive = fs . readFileSync ( 'apps/web/src/responsive.css' , 'utf8' ) ;
assert . match ( responsive , /prefers-reduced-motion:reduce\)\{\.computer-signal,\.computer-signal::before,\.computer-signal-face i[^}]*animation:none/ , 'reduced motion still has to still the mascot' ) ;
} ) ;
test ( 'a desktop that is still starting is dialled again fast, a dropped session calmly' , ( ) => {
const source = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
// Any element the viewer touches answers, so the disconnect path runs the
// same way it does in a browser without a DOM to stand in.
const element = ( ) => new Proxy ( { style : { } , classList : { toggle ( ) { } } , dataset : { } , hidden : false , textContent : '' } , { get : ( node , key ) => key in node ? node [ key ] : ( ) => undefined , set : ( node , key , value ) => { node [ key ] = value ; return true } } ) ;
const instances = [ ] , scheduled = [ ] ;
class RFB { constructor ( ) { this . _handlers = { } ; instances . push ( this . _handlers ) ; }
addEventListener ( name , handler ) { ( this . _handlers [ name ] || ( this . _handlers [ name ] = [ ] ) ) . push ( handler ) ; } focus ( ) { } blur ( ) { } sendKey ( ) { } }
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent : { postMessage ( ) { } } , addEventListener ( ) { } } ;
vm . runInNewContext ( source , { window , document : { location : { href : 'http://localhost/vnc.html?view_only=false' } , getElementById : ( ) => element ( ) , querySelector : ( ) => null } , navigator : { clipboard : { } } , RFB , setTimeout : ( fn , ms ) => { scheduled . push ( ms ) ; return scheduled . length } , clearTimeout ( ) { } } ) ;
const fire = ( name , event ) => { for ( const handlers of instances ) ( handlers [ name ] || [ ] ) . forEach ( handler => handler ( event || { } ) ) ; } ;
assert . equal ( instances . length , 1 , 'the viewer dials once on load' ) ;
fire ( 'disconnect' , { clean : false } ) ;
assert . equal ( scheduled . at ( - 1 ) , 350 , 'a desktop that has never answered is retried eagerly' ) ;
fire ( 'connect' ) ;
fire ( 'disconnect' , { clean : true } ) ;
assert . equal ( scheduled . at ( - 1 ) , 1500 , 'a session that was really there is retried calmly' ) ;
} ) ;
2026-09-08 09:14:15 +00:00
for ( const hostUpdatesDuringReconnect of [ false , true ] ) test ( ` reconnect preserves shared input (host update during reconnect: ${ hostUpdatesDuringReconnect } ) ` , ( ) => {
2026-09-08 01:14:53 +00:00
const vnc = fs . readFileSync ( 'apps/web/vnc.html' , 'utf8' ) ;
// Run the viewer rather than read it. The host hands the mouse over while the
// viewer sits between two sessions: exactly the moment a fresh RFB used to
// fall back to its view-only default and leave a taken-over desktop deaf with
// the veil already lifted. Only the gate message arriving is real here.
const body = vnc . match ( /<script type="module">([\s\S]*?)<\/script>/ ) [ 1 ] . replace ( /import RFB[^;]+;/ , '' ) ;
const element = ( ) => new Proxy ( { style : { } , classList : { toggle ( ) { } } , dataset : { } , hidden : false , textContent : '' } , { get : ( node , key ) => key in node ? node [ key ] : ( ) => undefined , set : ( node , key , value ) => { node [ key ] = value ; return true } } ) ;
const instances = [ ] , scheduled = [ ] , messages = [ ] ;
class RFB { constructor ( ) { this . _handlers = { } ; instances . push ( this ) ; } addEventListener ( name , handler ) { ( this . _handlers [ name ] || ( this . _handlers [ name ] = [ ] ) ) . push ( handler ) ; } focus ( ) { this . focused = true ; } blur ( ) { } }
const parent = { postMessage ( ) { } } ;
const window = { location : { pathname : '/vnc.html' , protocol : 'http:' , host : 'localhost' , origin : 'http://localhost' , hash : '' } , parent , addEventListener ( name , handler ) { if ( name === 'message' ) messages . push ( handler ) ; } } ;
vm . runInNewContext ( body , { window , document : { location : { href : 'http://localhost/vnc.html' } , getElementById : ( ) => element ( ) , querySelector : ( ) => null } , navigator : { clipboard : { } } , RFB , setTimeout : ( fn ) => { scheduled . push ( fn ) ; return scheduled . length } , clearTimeout ( ) { } } ) ;
const fire = ( name , event ) => instances . forEach ( rfb => ( rfb . _handlers [ name ] || [ ] ) . forEach ( handler => handler ( event || { } ) ) ) ;
assert . ok ( messages . length , 'the viewer listens for the host gate' ) ;
2026-09-08 09:14:15 +00:00
const allowInput = ( ) => messages . forEach ( handler => handler ( { origin : 'http://localhost' , source : parent , data : { type : 'lazyboy-view-only' , viewOnly : false } } ) ) ;
allowInput ( ) ;
fire ( 'disconnect' , { clean : false } ) ;
if ( hostUpdatesDuringReconnect ) allowInput ( ) ;
2026-09-08 01:14:53 +00:00
scheduled . at ( - 1 ) ( ) ;
fire ( 'connect' ) ;
assert . equal ( instances . length , 2 , 'the retry dialed a new session' ) ;
assert . equal ( instances . at ( - 1 ) . viewOnly , false , 'a handoff made mid-reconnect still owns the new session' ) ;
assert . equal ( instances . at ( - 1 ) . focused , true , 'taking over lands the first keystroke' ) ;
} ) ;
2026-09-09 06:41:09 +00:00
// Who speaks in a room is decided by three small pure functions, so the `@`
// list is run rather than read: which token the caret is writing, who that token
// could mean, and what the composer holds after a pick.
const mentionsJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/mentions.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const mentionsBox = { exports : { } , require : ( ) => { throw new Error ( 'unexpected import' ) } } ;
vm . runInNewContext ( mentionsJs , mentionsBox ) ;
const { mentionToken , mentionChoices , acceptMention } = mentionsBox . exports ;
const EVERYONE = '所有人' ;
const roster = [ { id : 'b1' , name : '小美' } , { id : 'b2' , name : 'Alex' } , { id : 'b3' , name : 'Alexa' } , { id : 'b4' , name : '工程小助手' } ] ;
// The list is built inside a vm, so its arrays are not this realm's arrays:
// compare what a person sees (names, joined) rather than the objects themselves.
const names = ( token , source = roster ) => mentionChoices ( token , source , EVERYONE ) . map ( choice => choice . name ) . join ( ',' ) ;
test ( 'a mention list opens only on the token under the caret' , ( ) => {
assert . equal ( mentionToken ( '@' , 1 ) , '' , 'a bare @ is still a list, not a word' ) ;
assert . equal ( mentionToken ( '@小美' , 3 ) , '小美' ) ;
assert . equal ( mentionToken ( '嗨 @小美 @' , 7 ) , '' , 'the last word wins, the earlier mention is spent' ) ;
assert . equal ( mentionToken ( '@小美 幫我看' , 4 ) , null , 'a finished name closes the list' ) ;
assert . equal ( mentionToken ( '幫我改一下佈景' , 6 ) , null , 'plain text offers nobody' ) ;
assert . equal ( mentionToken ( 'mail@example.com' , 16 ) , null , 'an email address is not a mention' ) ;
assert . equal ( mentionToken ( '@@' , 2 ) , null , 'a doubled @ is a typo, not a name' ) ;
} ) ;
test ( 'the whole-room escape hatch is the first name you are offered' , ( ) => {
const open = mentionChoices ( '' , roster , EVERYONE ) ;
assert . equal ( open [ 0 ] . name , EVERYONE , '@ comes with the escape hatch on top' ) ;
assert . equal ( open [ 0 ] . member , null , 'it reaches the room, so it is nobody in particular' ) ;
assert . equal ( names ( '' ) . split ( ',' ) . length , roster . length + 1 , 'then the whole room, in order' ) ;
assert . equal ( names ( '' ) . split ( ',' ) [ 1 ] , roster [ 0 ] . name ) ;
assert . equal ( names ( '所' ) , EVERYONE , 'a token that cannot grow into 所有人 drops it' ) ;
assert . equal ( names ( EVERYONE ) . split ( ',' ) [ 0 ] , EVERYONE , 'typing it out keeps it offered' ) ;
} ) ;
test ( 'mention choices match loosely and stay one glance long' , ( ) => {
assert . equal ( names ( 'alex' ) , 'Alex,Alexa' , 'case does not matter, room order does' ) ;
assert . equal ( names ( '小' ) , '小美,工程小助手' , 'a name anywhere in the word hits' ) ;
assert . equal ( names ( ' nobody ' ) , '' , 'a token nobody can answer to offers nobody' ) ;
const crowd = Array . from ( { length : 9 } , ( _ , index ) => ( { id : ` b ${ index } ` , name : ` bot ${ index } ` } ) ) ;
assert . equal ( names ( '' , crowd ) . split ( ',' ) . length , 6 , 'six rows is the cap' ) ;
assert . equal ( names ( '' , crowd ) . split ( ',' ) [ 0 ] , EVERYONE , 'and the escape hatch still leads' ) ;
} ) ;
test ( 'accepting a mention leaves the sentence readable and the list closed' , ( ) => {
const fresh = acceptMention ( '@' , 1 , '小美' ) ;
assert . equal ( fresh . text , '@小美 ' ) ;
assert . equal ( fresh . caret , fresh . text . length , 'the caret sits past the space, so typing continues the sentence' ) ;
assert . equal ( mentionToken ( fresh . text , fresh . caret ) , null , 'and the list does not reopen behind it' ) ;
const picked = acceptMention ( '幫我 @小 部署' , 5 , '小美' ) ;
assert . ok ( picked . text . startsWith ( '幫我 @小美 ' ) , 'only the typed token is replaced' ) ;
assert . ok ( picked . text . includes ( '部署' ) , 'the rest of the message survives' ) ;
assert . equal ( acceptMention ( '@小' , 2 , '小美' ) . text , '@小美 ' , 'a half-typed name is replaced whole' ) ;
} ) ;
// When a message was said is split the way chat apps split it: the day once,
// as a divider, and the clock time on each message. The words for today and
// yesterday come from the catalog; everything else is the locale's own spelling.
const chatTimeJs = ts . transpileModule ( fs . readFileSync ( 'apps/web/src/chat-time.ts' , 'utf8' ) , { compilerOptions : { module : ts . ModuleKind . CommonJS } } ) . outputText ;
const chatTimeBox = { exports : { } , require : ( ) => { throw new Error ( 'unexpected import' ) } } ;
vm . runInNewContext ( chatTimeJs , chatTimeBox ) ;
const { dayLabel , sameDay , clockTime } = chatTimeBox . exports ;
const words = { today : zhTW . today , yesterday : zhTW . yesterday } ;
// A Wednesday afternoon, built in local time so the calendar day is unambiguous.
const now = new Date ( 2026 , 9 , 7 , 15 , 30 ) ;
const daysAgo = ( count , hour = 9 ) => new Date ( 2026 , 9 , 7 - count , hour ) ;
test ( 'a day is named the way people say it' , ( ) => {
assert . equal ( dayLabel ( daysAgo ( 0 , 0 ) , now , 'zh-TW' , words ) , '今天' , 'midnight still counts as today' ) ;
assert . equal ( dayLabel ( daysAgo ( 0 , 23 ) , now , 'zh-TW' , words ) , '今天' , 'a message later today is still today, not the future' ) ;
assert . equal ( dayLabel ( daysAgo ( 1 , 23 ) , now , 'zh-TW' , words ) , '昨天' , 'yesterday means the calendar day, not 24 hours' ) ;
assert . equal ( dayLabel ( daysAgo ( 1 ) , now , 'en' , { today : en . today , yesterday : en . yesterday } ) , 'Yesterday' ) ;
assert . equal ( dayLabel ( daysAgo ( 2 ) , now , 'zh-TW' , words ) , '星期一' , 'the past week goes by weekday' ) ;
assert . equal ( dayLabel ( daysAgo ( 6 ) , now , 'zh-TW' , words ) , '星期四' , 'six days back is the last weekday' ) ;
const week = dayLabel ( daysAgo ( 7 ) , now , 'zh-TW' , words ) ;
assert . match ( week , /9\/30|9月30日/ , 'a week or more ago is written as a date' ) ;
assert . match ( week , /週三|星期三/ , 'with its weekday, so it reads like a chat and not a form' ) ;
assert . ok ( ! /2026/ . test ( week ) , 'this year is not spelled out' ) ;
assert . match ( dayLabel ( new Date ( 2025 , 9 , 1 ) , now , 'zh-TW' , words ) , /2025/ , 'another year is spelled out' ) ;
assert . match ( dayLabel ( new Date ( 2026 , 8 , 15 ) , now , 'en' , { today : en . today , yesterday : en . yesterday } ) , /Tue, 9\/15/ , 'English keeps the same short numeric date' ) ;
assert . equal ( dayLabel ( 'not a date' , now , 'zh-TW' , words ) , '' , 'garbage shows nothing rather than Invalid Date' ) ;
} ) ;
test ( 'messages split by calendar day and show only their clock time' , ( ) => {
assert . ok ( sameDay ( new Date ( 2026 , 9 , 7 , 0 , 1 ) , new Date ( 2026 , 9 , 7 , 23 , 59 ) ) , 'first and last minute share a day' ) ;
assert . ok ( ! sameDay ( new Date ( 2026 , 9 , 7 , 23 , 59 ) , new Date ( 2026 , 9 , 8 , 0 , 0 ) ) , 'a minute past midnight is a new day' ) ;
assert . ok ( ! sameDay ( 'garbage' , 'garbage' ) , 'two unreadable dates are not the same day' ) ;
assert . equal ( clockTime ( new Date ( 2026 , 9 , 7 , 9 , 5 ) , 'zh-TW' ) , '09:05' , '24-hour clock, zero padded' ) ;
assert . equal ( clockTime ( new Date ( 2026 , 9 , 7 , 15 , 30 ) , 'en' ) , '15:30' ) ;
assert . equal ( clockTime ( 'garbage' , 'en' ) , '' ) ;
} ) ;