Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/ui-test.mjs

Raw
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { basename, dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { stripVTControlCharacters } from 'node:util';
import { setTimeout as delay } from 'node:timers/promises';
import { prepareNiriIntegration, runNiriIntegration } from './test-fixtures/ui/niri-integration.mjs';
import { taskSelection, taskOverlayFixtures, taskEntryFixture, runTasks } from './test-fixtures/ui/tasks.mjs';
import { runResourceProbe } from './test-fixtures/ui/resource-probe.mjs';

const project = dirname(fileURLToPath(import.meta.url));

export function childEnvironment(runtime, display, socket) {
    const directory = realpathSync(runtime);
    for (const path of [display, socket]) {
        assert.equal(dirname(realpathSync(path)), directory, 'refusing non-child socket');
        assert(lstatSync(path).isSocket(), 'child endpoint must be a socket');
    }
    assert.match(basename(display), /^wayland-\d+$/);
    assert.match(basename(socket), /^niri\..*\.sock$/);
    return { ...process.env, WAYLAND_DISPLAY: display, NIRI_SOCKET: socket, XDG_RUNTIME_DIR: directory };
}

export function assertCleanLog(text) {
    assert(!/\b(?:WARN(?:ING)?|ERROR)\b|Error parsing|binding loop|ReferenceError|TypeError|deprecated|Could not|failed/i.test(text), text);
}

// Owner-approved sandbox exceptions only. Preserve raw logs and fail every
// other diagnostic, including these same messages at another level or source.
export function assertNiriLog(text, writeFailure = null) {
    const fallback = 'failed building default dmabuf feedback, falling back to v3: error getting EGL device render node\n\nCaused by:\n    None of the following EGL extensions is supported by the underlying EGL implementation, at least one is required: ["EGL_EXT_device_drm"]';
    const saving = 'saving screenshot to "/evidence/missing-directory/native-failure.png"';
    const failedWrite = 'error saving screenshot image: Os { code: 2, kind: NotFound, message: "No such file or directory" }';
    if (writeFailure) {
        assert(Number.isSafeInteger(writeFailure.start) && Number.isSafeInteger(writeFailure.end));
        assert(writeFailure.start >= 0 && writeFailure.start < writeFailure.end && writeFailure.end <= text.length, 'invalid expected diagnostic interval');
        for (const offset of [writeFailure.start, writeFailure.end])
            assert(offset === 0 || text[offset - 1] === '\n', 'diagnostic interval must end on record boundaries');
    }
    const parts = writeFailure ? [text.slice(0, writeFailure.start), text.slice(writeFailure.start, writeFailure.end), text.slice(writeFailure.end)] : [text];
    const result = { softwareFallbacks: 0, expectedWriteFailures: 0 };
    for (const [index, part] of parts.entries()) {
        let previous = null;
        const records = stripVTControlCharacters(part).split(/(?=^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d+Z\s+(?:TRACE|DEBUG|INFO|WARN|ERROR)\s)/m);
        for (const record of records) {
            const match = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d+Z\s+(TRACE|DEBUG|INFO|WARN|ERROR)\s+(\S+): ([\s\S]*?)\s*$/.exec(record);
            const [, level, source, message] = match || [];
            if (level === 'DEBUG' && source === 'niri::backend::winit' && message === fallback) {
                assert(++result.softwareFallbacks <= 1, 'duplicate software fallback');
            } else if (writeFailure && index === 1 && level === 'WARN' && source === 'niri::niri' && message === failedWrite) {
                assert.deepEqual(previous, ['DEBUG', 'niri::niri', saving], 'write warning must follow the deliberate destination');
                assert(++result.expectedWriteFailures === 1, 'duplicate expected write warning');
            } else assertCleanLog(record);
            previous = match ? [level, source, message] : null;
        }
    }
    assert.equal(result.expectedWriteFailures, writeFailure ? 1 : 0, 'missing deliberate write warning');
    return result;
}

export function assertPureService(text) {
    assert.match(text, /^import QtQuick\s+Item\s*\{/);
    assert.equal((text.match(/\bimport\b/g) || []).length, 1, 'mock imports must be QtQuick only');
    assert.deepEqual(text.match(/\b[A-Z]\w*\s*\{/g), ['Item {'], 'mock must contain only Item state/functions');
    assert(!/\b(?:Qt|Quickshell|Process|FileView|XMLHttpRequest|WebSocket|WorkerScript|PolkitAgent|PamContext|fetch|eval|Function)\b/.test(text), 'privileged or dynamic mock API');
}

export async function stopChild(child) {
    if (!child.pid || child.exitCode !== null || child.signalCode !== null) return;
    const closed = new Promise(resolve => child.once('close', resolve));
    child.kill('SIGTERM');
    const force = setTimeout(() => child.kill('SIGKILL'), 2000);
    try { await closed; } finally { clearTimeout(force); }
}

export function replayRequest(args) {
    const fixture = args[0] ?? 'all';
    const size = args[1];
    assert(args.length <= 2 && ['all', 'controls', 'popouts', 'system', 'privilege', 'noko', 'niri', taskSelection].includes(fixture)
        && (size === undefined || fixture === taskSelection && ['1366x768', '2560x1440'].includes(size)),
        'usage: just ui-test [all|controls|popouts|system|privilege|noko|niri|tasks] [tasks: 1366x768|2560x1440]');
    return { version: 1, fixture, command: `just ui-test ${fixture}${size ? ` ${size}` : ''}`, ...(size ? { size } : {}) };
}

export function assertSafeCapture(snapshot) {
    assert(snapshot, 'snapshot unavailable for safe screenshot');
    if (snapshot.auth?.visible) {
        assert(snapshot.auth.hasInput === false || snapshot.auth.inputMasked === true, 'refusing screenshot of unmasked input');
    }
}

export async function captureUiFailure(error, { evidence, fixture, phase, snapshot, screenshot, write = writeFileSync }) {
    const record = { version: 1, fixture, phase, error: error.stack || String(error), snapshot, screenshot: null, captureErrors: [] };
    try { record.screenshot = await screenshot('failure'); }
    catch (captureError) { record.captureErrors.push(captureError.stack || String(captureError)); }
    write(join(evidence, 'failure.json'), JSON.stringify(record, null, 2));
    return record;
}

export async function finishUiRun(failure, { children, checkLogs, capture, stop = stopChild, report = error => console.error(error.stack || String(error)) }) {
    let captured = false;
    async function retainFailure() {
        if (!failure || captured) return;
        captured = true;
        try { await capture(failure); } catch (error) { report(error); }
    }
    // The first gate runs while niri is alive so infrastructure failures have pixels too.
    try { checkLogs(); } catch (error) { failure ||= error; }
    await retainFailure();
    for (const child of [...children].reverse()) {
        try { await stop(child); }
        catch (error) { if (failure) report(error); else failure = error; }
    }
    // Retain the original complete-log gate, including diagnostics emitted on shutdown.
    try { checkLogs(); } catch (error) { failure ||= error; }
    await retainFailure();
    if (failure) throw failure;
}

async function outer(replay) {
    mkdirSync(join(project, '.tmp'), { recursive: true });
    const evidence = mkdtempSync(join(project, '.tmp/ui-test-'));
    const runtime = mkdtempSync(join(tmpdir(), 'nuguland-ui-runtime-'));
    writeFileSync(join(runtime, 'passwd'), `test:x:${process.getuid()}:${process.getgid()}:test:/home/test:/bin/sh\n`);
    const args = [
        '--unshare-all', '--die-with-parent', '--new-session',
        '--ro-bind', '/usr', '/usr', '--symlink', 'usr/bin', '/bin',
        '--symlink', 'usr/lib', '/lib', '--symlink', 'usr/lib', '/lib64',
        '--proc', '/proc', '--dev', '/dev', '--tmpfs', '/tmp',
        '--ro-bind', '/etc/fonts', '/etc/fonts',
        '--ro-bind', '/etc/ld.so.cache', '/etc/ld.so.cache',
        '--ro-bind', '/etc/machine-id', '/etc/machine-id',
        '--ro-bind', join(runtime, 'passwd'), '/etc/passwd',
        '--dir', '/home/test', '--dir', '/run/user',
        '--overlay-src', project, '--tmp-overlay', '/project', '--bind', evidence, '/evidence',
        '--bind', runtime, '/run/user/1000',
        '--chdir', '/project', '--clearenv',
        '--setenv', 'PATH', '/usr/bin', '--setenv', 'HOME', '/home/test',
        '--setenv', 'XDG_RUNTIME_DIR', '/run/user/1000',
        '--setenv', 'XDG_CONFIG_HOME', '/home/test/.config',
        '--setenv', 'XDG_CACHE_HOME', '/home/test/.cache',
        '--setenv', 'XDG_DATA_HOME', '/home/test/.local/share',
        '--setenv', 'XDG_STATE_HOME', '/home/test/.local/state',
        '--setenv', 'QT_QPA_PLATFORM', 'wayland',
        '--setenv', 'LIBGL_ALWAYS_SOFTWARE', '1',
        '--setenv', 'NUGULAND_UI_SANDBOX', '1',
        'dbus-run-session', '--', 'node', '/project/ui-test.mjs', '--inside', replay.fixture, ...(replay.size ? [replay.size] : [])
    ];
    console.log(`evidence: ${evidence}`);
    writeFileSync(join(evidence, 'replay.json'), JSON.stringify({ ...replay, phase: 'startup' }, null, 2));
    writeFileSync(join(evidence, 'command.json'), JSON.stringify({ cwd: project, command: ['bwrap', ...args] }, null, 2));
    const child = spawn('bwrap', args, { stdio: ['ignore', 'pipe', 'pipe'] });
    for (const stream of [child.stdout, child.stderr]) stream.on('data', data => {
        appendFileSync(join(evidence, 'runner.log'), data);
        process.stdout.write(data);
    });
    let interrupted = false;
    const interrupt = () => {
        if (interrupted) return;
        interrupted = true;
        void stopChild(child);
    };
    process.on('SIGINT', interrupt);
    process.on('SIGTERM', interrupt);
    const timeout = setTimeout(interrupt, 300_000);
    try {
        const code = await new Promise((resolve, reject) => {
            child.once('error', reject);
            child.once('close', (code, signal) => resolve(signal ? -1 : code));
        });
        assert(!interrupted, `nested UI test interrupted; full logs: ${evidence}`);
        assert.equal(code, 0, `nested UI test failed; full logs: ${evidence}`);
    } catch (error) {
        // An inner failure already owns its evidence; never replace it with bwrap's exit code.
        if (!existsSync(join(evidence, 'failure.json'))) {
            try {
                const { phase } = JSON.parse(readFileSync(join(evidence, 'replay.json'), 'utf8'));
                await captureUiFailure(error, {
                    evidence, fixture: replay.fixture, phase, snapshot: null,
                    screenshot: async () => { throw new Error('sandbox unavailable for capture'); }
                });
            } catch (captureError) { console.error(captureError.stack); }
        }
        throw error;
    } finally {
        clearTimeout(timeout);
        process.off('SIGINT', interrupt);
        process.off('SIGTERM', interrupt);
        await stopChild(child);
        rmSync(runtime, { recursive: true });
        assert(!existsSync(runtime), 'owned runtime was not removed');
    }
}

async function inside(replay) {
    assert.equal(process.env.NUGULAND_UI_SANDBOX, '1', 'internal mode requires sandbox');
    assert.equal(process.env.HOME, '/home/test');
    assert.equal(process.env.WAYLAND_DISPLAY, undefined, 'parent Wayland connection must not be inherited');
    assert.equal(process.env.DISPLAY, undefined, 'parent X11 connection must not be inherited');
    assert(!existsSync('/run/dbus/system_bus_socket'), 'host system bus must be inaccessible');
    assert(!existsSync('/home/oli'), 'host HOME must be inaccessible');
    assert(process.env.DBUS_SESSION_BUS_ADDRESS, 'private session bus required');
    const evidence = '/evidence';
    const children = [];
    const runtimeLogs = [];
    let fixtureLog = '';
    let phase = 'startup';
    let latestSnapshot = null;
    let fixtureProcess = null;
    let expectedWriteFailure = null;
    let snapshot = () => { throw new Error('fixture snapshot not ready'); };
    let failureScreenshot = async () => { throw new Error('child compositor not ready'); };
    const controls = replay.fixture === 'all' || replay.fixture === 'controls';
    const refactor = !['controls', 'niri', taskSelection].includes(replay.fixture);
    const tasks = replay.fixture === 'all' || replay.fixture === taskSelection;
    const integration = replay.fixture === 'all' || replay.fixture === 'niri';
    const nativeNiri = readFileSync('/project/Niri.qml', 'utf8');
    const nativeResources = readFileSync('/project/Resources.qml', 'utf8');
    const taskOriginals = new Map(taskOverlayFixtures.map(({ target }) => [target, readFileSync(`/project/${target}`, 'utf8')]));
    const [outputWidth, outputHeight] = (replay.size || '1280x720').split('x').map(Number);
    function prepareTasks() {
        for (const { source, target } of taskOverlayFixtures) {
            const text = readFileSync(`/project/${source}`, 'utf8');
            assertPureService(text);
            writeFileSync(`/project/${target}`, text);
        }
        writeFileSync(`/project/${taskEntryFixture.target}`, readFileSync(`/project/${taskEntryFixture.source}`, 'utf8').replace(taskEntryFixture.replace.from, taskEntryFixture.replace.to));
    }
    function enterPhase(name) {
        phase = name;
        writeFileSync(join(evidence, 'replay.json'), JSON.stringify({ ...replay, phase }, null, 2));
        console.log(`phase: ${name} (fixture: ${replay.fixture})`);
    }
    // Compositor system-bus watchers use the private bus, never the host bus.
    let env = { ...process.env, DBUS_SYSTEM_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS };
    function run(command, args = []) {
        const result = spawnSync(command, args, { env, encoding: 'utf8', timeout: 30_000 });
        appendFileSync(join(evidence, 'commands.log'), `${JSON.stringify([command, ...args])}\n${result.stdout || ''}${result.stderr || ''}exit: ${result.status}\n`);
        if (result.error) throw result.error;
        assert.equal(result.status, 0, `${command}: ${result.stdout}${result.stderr}`);
        return result.stdout.trim();
    }
    function start(command, args, name, childEnv = env) {
        appendFileSync(join(evidence, 'commands.log'), `start: ${JSON.stringify([command, ...args])}\n`);
        const child = spawn(command, args, { env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
        const log = join(evidence, `${name}.log`);
        writeFileSync(log, '');
        runtimeLogs.push(log);
        for (const stream of [child.stdout, child.stderr]) stream.on('data', data => appendFileSync(log, data));
        child.on('error', error => { child.failure = error; });
        children.push(child);
        return child;
    }
    async function waitFor(label, predicate) {
        const deadline = Date.now() + 30_000;
        while (Date.now() < deadline) {
            for (const child of children) {
                if (child.failure) throw child.failure;
                assert.equal(child.exitCode, null, `child exited during ${label}; inspect logs`);
                assert.equal(child.signalCode, null, `child signalled during ${label}; inspect logs`);
            }
            if (fixtureLog) assertCleanLog(readFileSync(fixtureLog, 'utf8'));
            const value = predicate();
            if (value) return value;
            await delay(50);
        }
        throw new Error(`timeout: ${label}`);
    }
    let failure = null;
    try {
        enterPhase('startup');
        // Drop a host-generated symlink only in the disposable overlay.
        rmSync('/project/.qmlls.ini', { force: true });
        writeFileSync('/project/.qmlls.ini', '');
        writeFileSync(join(evidence, 'versions.txt'), [run('weston', ['--version']), run('niri', ['--version']), run('qs', ['--version']), run('wdotool', ['--version']), run('/usr/lib/qt6/bin/qmllint', ['--version'])].join('\n') + '\n');
        const runtime = process.env.XDG_RUNTIME_DIR;
        const busFlags = run('pkg-config', ['--cflags', '--libs', 'libsystemd']).split(/\s+/);
        run('cc', ['-std=c17', '-Wall', '-Wextra', '-Werror', '/project/test-fixtures/ui/session-bus.c', ...busFlags, '-o', '/tmp/nuguland-session-bus']);
        start('/tmp/nuguland-session-bus', [], 'session-bus');
        await waitFor('private login/locale fixtures', () => readFileSync(join(evidence, 'session-bus.log'), 'utf8').includes('session fixtures ready'));
        const westonSocket = join(runtime, 'weston-headless');
        const westonModules = [];
        if (integration) {
            const flags = run('pkg-config', ['--cflags', '--libs', 'libweston-15', 'wayland-server', 'xkbcommon']).split(/\s+/);
            run('cc', ['-std=gnu17', '-Wall', '-Wextra', '-Werror', '-shared', '-fPIC', '/project/test-fixtures/ui/niri-integration-keyboard.c', ...flags, '-o', '/tmp/niri-integration-keyboard.so']);
            run('mkfifo', [join(runtime, 'niri-integration-keyboard.fifo')]);
            westonModules.push('--modules=/tmp/niri-integration-keyboard.so');
        }
        start('weston', ['--backend=headless', '--renderer=pixman', '--shell=kiosk', '--socket=weston-headless', `--width=${outputWidth}`, `--height=${outputHeight}`, '--idle-time=0', '--no-config', '--fake-seat', ...westonModules], 'weston');
        await waitFor('headless Weston socket', () => existsSync(westonSocket) && lstatSync(westonSocket).isSocket());
        env.WAYLAND_DISPLAY = westonSocket;
        run('niri', ['validate', '--config', '/project/test-fixtures/ui/niri.kdl']);
        const compositor = start('niri', ['--config', '/project/test-fixtures/ui/niri.kdl'], 'niri');
        const endpoints = await waitFor('nested compositor sockets', () => {
            const files = readdirSync(runtime);
            const display = files.find(name => /^wayland-\d+$/.test(name));
            const socket = files.find(name => /^niri\..*\.sock$/.test(name));
            return display && socket ? [join(runtime, display), join(runtime, socket)] : null;
        });
        env = { ...env, ...childEnvironment(runtime, ...endpoints), DBUS_SYSTEM_BUS_ADDRESS: env.DBUS_SYSTEM_BUS_ADDRESS };
        await waitFor('Niri consumed private login/locale properties', () => {
            const log = readFileSync(join(evidence, 'session-bus.log'), 'utf8');
            return ['org.freedesktop.login1.Manager.LidClosed', ...['X11Model', 'X11Layout', 'X11Variant', 'X11Options'].map(name => `org.freedesktop.locale1.${name}`)].every(name => log.includes(`read ${name}\n`));
        });
        assert.equal(run('busctl', ['--address', env.DBUS_SESSION_BUS_ADDRESS, 'get-property', 'org.freedesktop.login1', '/org/freedesktop/login1', 'org.freedesktop.login1.Manager', 'LidClosed']), 'b false');
        assert.equal(run('busctl', ['--address', env.DBUS_SESSION_BUS_ADDRESS, 'get-property', 'org.freedesktop.locale1', '/org/freedesktop/locale1', 'org.freedesktop.locale1', 'X11Layout']), 's "us"');
        assert.notEqual(env.WAYLAND_DISPLAY, westonSocket, 'input must target niri, not Weston');
        async function screenshot(name) {
            // Never change the failing UI to make evidence look safe or successful.
            // Password-mode pixels are already masked; revealed input refuses capture.
            await delay(300);
            assert.equal(compositor.exitCode, null, 'child compositor exited before capture');
            assert.equal(compositor.signalCode, null, 'child compositor signalled before capture');
            childEnvironment(runtime, ...endpoints);
            if (fixtureProcess?.pid && fixtureProcess.exitCode === null && fixtureProcess.signalCode === null) assertSafeCapture(snapshot());
            const path = join(evidence, `${name}.png`);
            run('niri', ['msg', 'action', 'screenshot-screen', '--show-pointer', 'false', '--path', path]);
            // Failure capture must not run the fixture-log gate that caused the failure.
            const deadline = Date.now() + 3000;
            while (Date.now() < deadline) {
                if (existsSync(path) && lstatSync(path).size > 0) return path;
                await delay(50);
            }
            throw new Error(`timeout: screenshot ${name}`);
        }
        failureScreenshot = screenshot;
        writeFileSync(join(evidence, 'outputs.json'), run('niri', ['msg', '--json', 'outputs']));
        start('wdotool', ['--backend', 'wlr-protocols', 'prime'], 'input');
        await waitFor('virtual input ready', () => readFileSync(join(evidence, 'input.log'), 'utf8').includes('ready'));
        if (refactor) {
            for (const name of ['CalDav', 'Resources', 'Privilege', 'Noko', 'Niri', 'Audio', 'Network', 'Vault']) {
                const source = `/project/test-fixtures/ui/services/${name}.qml`;
                assertPureService(readFileSync(source, 'utf8'));
                copyFileSync(source, `/project/${name}.qml`);
            }
            // Keep the scanner root at /project, as for the original shared fixture.
            writeFileSync('/project/refactor-ui-test.qml', readFileSync('/project/test-fixtures/ui/refactor.qml', 'utf8').replace('import "../.." as N', 'import "." as N'));
        }
        let integrationSetup;
        async function prepareIntegration() {
            integrationSetup = await prepareNiriIntegration({ run, start, waitFor, runtime, nativeNiri, nativeSocket: endpoints[1], assertPureService });
            childEnvironment(runtime, endpoints[0], integrationSetup.socket);
        }
        if (replay.fixture === 'niri') await prepareIntegration();
        if (replay.fixture === taskSelection) prepareTasks();
        let fixture = controls ? '/project/ui-test.qml' : refactor ? '/project/refactor-ui-test.qml' : tasks ? '/project/tasks-ui-test.qml' : '/project/niri-integration-ui-test.qml';
        const firstLog = controls ? 'quickshell' : refactor ? 'refactor' : tasks ? 'tasks' : 'niri-integration';
        fixtureProcess = start('qs', ['--no-color', '-p', fixture], firstLog);
        fixtureLog = join(evidence, `${firstLog}.log`);
        await waitFor('quickshell loaded', () => {
            const log = readFileSync(fixtureLog, 'utf8');
            assertCleanLog(log);
            return log.includes('Configuration Loaded');
        });
        const qmllsConfig = await waitFor('generated qmlls configuration', () => {
            const config = readFileSync('/project/.qmlls.ini', 'utf8');
            return config.includes('importPaths=') && config.includes('buildDir=') ? config : null;
        });
        writeFileSync(join(evidence, 'qmlls.ini'), qmllsConfig);
        console.log('pass: Quickshell generated its upstream-recommended qmlls configuration');
        const ipc = (...args) => run('qs', ['ipc', '-p', fixture, 'call', 'uiTest', ...args]);
        snapshot = () => {
            const text = ipc('snapshot');
            assert(!text.includes('fixture-only'), 'snapshot exposed synthetic input');
            const state = JSON.parse(text);
            latestSnapshot = state;
            return state;
        };
        const input = (...args) => run('wdotool', ['--backend', 'wlr-protocols', ...args]);
        const key = key => input('key', key);
        const click = rect => {
            input('mousemove', String(Math.round(rect.x + rect.width / 2)), String(Math.round(rect.y + rect.height / 2)));
            input('click', '1');
        };
        async function check(label, predicate) {
            const state = await waitFor(label, () => { const state = snapshot(); return predicate(state) ? state : null; });
            appendFileSync(join(evidence, 'assertions.jsonl'), JSON.stringify({ label, state }) + '\n');
            console.log(`pass: ${label}`);
            return state;
        }
        let state;
        if (controls) {
            enterPhase('controls');
            state = await check('initial bindings and focus', s => !s.muted && !s.checked && s.volume === 0.3 && s.sliderValue === 0.3 && s.focus === 'mute');
            assert.equal(state.width, 1280, 'headless output width');
            assert.equal(state.height, 720, 'headless output height');
            for (const name of ['surface', 'mute', 'slider', 'field']) {
                const rect = state[name];
                assert(rect.x >= 0 && rect.y >= 0 && rect.width > 0 && rect.height > 0 && rect.x + rect.width <= state.width && rect.y + rect.height <= state.height, `${name} outside mapped surface`);
            }
            await screenshot('initial');
            click(state.mute);
            state = await check('pointer toggles owner and checked state', s => s.muted && s.checked && s.writes === 1);
            key('space');
            await check('space activates focused button', s => !s.muted && !s.checked && s.writes === 2);
            key('Return');
            await check('enter activates focused button', s => s.muted && s.checked && s.writes === 3);
            key('Tab');
            await check('tab reaches slider', s => s.focus === 'slider');
            key('Right');
            await check('arrow updates slider owner', s => Math.abs(s.volume - 0.35) < 0.001 && s.sliderValue === s.volume);
            click({ ...state.slider, width: state.slider.width * 1.5 });
            await check('pointer updates slider owner', s => Math.abs(s.volume - 0.75) < 0.02 && s.sliderValue === s.volume);
            key('Left');
            await check('left arrow decreases slider owner', s => Math.abs(s.volume - 0.7) < 0.02 && s.sliderValue === s.volume);
            key('Tab');
            await check('tab reaches text input', s => s.focus === 'field');
            input('type', 'headphones');
            await check('text input updates fixture query', s => s.queryIsHeadphones && s.queryMatchesInput);
            key('Return');
            await check('enter accepts text field', s => s.accepted === 1);
            key('Escape');
            await check('escape cancels text field', s => s.cancelled === 1);
            key('shift+Tab');
            await check('reverse traversal reaches slider', s => s.focus === 'slider');
            key('shift+Tab');
            state = await check('reverse traversal reaches button', s => s.focus === 'mute');
            const writes = state.writes;
            ipc('external', 'true', '0.6', 'false');
            state = await check('external state updates controls without writeback', s => s.muted && s.checked && s.volume === 0.6 && s.sliderValue === 0.6 && !s.enabled && s.writes === writes);
            click(state.mute);
            key('space');
            click(state.slider);
            key('Right');
            click(state.field);
            input('type', ' test');
            await check('disabled controls reject input before the next field edit', s => s.queryIsEdited && s.queryMatchesInput && s.writes === writes);
            input('mousemove', String(state.width - 10), String(state.height - 10));
            await check('pointer leaves button tooltip', s => !s.tooltipVisible);
            await screenshot('external-disabled');
            for (const value of [0, 0.25, 0.75, 1]) {
                ipc('external', 'false', String(value), 'true');
                state = await check(`external slider value ${value} is visible`, s => s.sliderValue === value);
                const before = state.writes;
                click(state.sliderThumb);
                state = await check(`pressing thumb at ${value} does not jump or write`, s => s.writes === before && s.volume === value && s.focus === 'slider');
            }
            input('mousemove', String(state.sliderThumb.x + state.sliderThumb.width / 2), String(state.sliderThumb.y + state.sliderThumb.height / 2));
            input('mousedown', '1');
            await check('thumb drag holds pointer grab', s => s.sliderPressed);
            for (const value of [0.75, 0.25, 0]) {
                input('mousemove', String(Math.round(state.slider.x + 8 + (state.slider.width - 16) * value)), String(state.slider.y + state.slider.height / 2));
                await check(`continuous thumb drag reaches ${value}`, s => s.sliderPressed && Math.abs(s.volume - value) < 0.01);
            }
            input('mouseup', '1');
            await check('thumb release ends drag', s => !s.sliderPressed);
            click({ ...state.extras.card, width: 36 });
            state = await check('interactive card primary activation is focused', s => s.extras.cardWrites === 1 && s.extras.cardFocused);
            key('Tab');
            await check('Tab reaches nested card action without focusing its parent', s => s.extras.nestedFocused && !s.extras.cardFocused);
            key('space');
            state = await check('nested action dispatches without parent activation', s => s.extras.nestedWrites === 1 && s.extras.cardWrites === 1);
            key('Tab');
            state = await check('Tab reaches named icon action', s => s.extras.iconFocused && s.extras.iconName === 'refresh devices' && s.extras.sliderName === 'output volume');
            input('mousemove', String(state.width - 10), String(state.height - 10));
            await check('keyboard focus exposes themed icon tooltip', s => s.extras.iconTooltipVisible);
            await screenshot('keyboard-tooltip');
            input('keydown', 'Return');
            await delay(800);
            state = await check('held Enter does not repeatedly dispatch', s => s.extras.iconWrites === 0);
            input('keyup', 'Return');
            await check('Enter release dispatches exactly once', s => s.extras.iconWrites === 1);
            input('keydown', 'space');
            await delay(800);
            await check('held Space does not repeatedly dispatch', s => s.extras.iconWrites === 1);
            input('keyup', 'space');
            await check('Space release dispatches exactly once', s => s.extras.iconWrites === 2);
            click(state.extras.disabled);
            await check('unavailable control never dispatches', s => s.extras.disabledWrites === 0);
            click({ ...state.extras.field, width: 4 });
            await check('field padding focuses editor with its empty hint', s => s.extras.fieldFocused && s.extras.fieldHintVisible);
            input('type', 'padding');
            await check('typing after padding click edits normally', s => s.extras.fieldEdited && !s.extras.fieldHintVisible);
            input('mousemove', ...[state.extras.longButton.x + 8, state.extras.longButton.y + 8].map(String));
            await check('long useful tooltip opens on pointer hover', s => s.extras.longTooltipVisible);
            await screenshot('wrapped-tooltip');
            input('mousemove', String(state.width - 10), String(state.height - 10));
            await check('leaving tooltip trigger dismisses it', s => !s.extras.longTooltipVisible);
            await screenshot('controls-repaired');
            assertCleanLog(readFileSync(join(evidence, 'quickshell.log'), 'utf8'));
            console.log('shared-control UI assertions passed; screenshots require visual review');

        }
        if (controls && refactor) {
            // Only the completed fixture stops. Weston, niri, input and the private bus stay alive.
            await stopChild(fixtureProcess);
            assertCleanLog(readFileSync(fixtureLog, 'utf8'));
            children.splice(children.indexOf(fixtureProcess), 1);
            latestSnapshot = null;
            fixture = '/project/refactor-ui-test.qml';
            fixtureProcess = start('qs', ['--no-color', '-p', fixture], 'refactor');
            fixtureLog = join(evidence, 'refactor.log');
            await waitFor('refactor fixture loaded', () => readFileSync(fixtureLog, 'utf8').includes('Configuration Loaded'));
        }
        const hasText = (rows, text) => rows.some(row => row.text === text);
        const within = (rect, width, height) => rect && rect.width > 0 && rect.height > 0 && rect.x >= 0 && rect.y >= 0 && rect.x + rect.width <= width && rect.y + rect.height <= height;
        function geometry(s, name) {
            const vertical = s.orientation === 'vertical';
            const width = name === 'clock' ? 760 : 780;
            const height = name === 'clock' ? 580 : 600;
            assert.deepEqual(s.popup.contentSize, { width, height });
            assert.deepEqual(s.card, {
                x: vertical ? 56 : Math.round((1280 - width) / 2) + 8,
                y: vertical ? Math.round((720 - height) / 2) + 8 : 56,
                width: width - 16, height: height - 16
            });
            assert(within(s.card, s.width, s.height));
            assert(within(s.panel, s.width, s.height));
            assert.equal(s.catalog.entries[name].component, name === 'clock' ? 'calendar-agenda' : 'system-resources');
        }
        function diagnostics() {
            const text = run('qs', ['ipc', '-p', fixture, 'call', 'nuguland', 'diagnostics']);
            assert(!text.includes('diagnostic-private-sentinel'), 'diagnostics exposed private fixture state');
            const data = JSON.parse(text);
            assert.equal(data.version, 1);
            assert.deepEqual(data.services.niri, { available: true, ready: true, windowCount: 1, workspaceCount: 1 });
            assert.deepEqual(data.services.vault, { status: 'locked', lockPending: false });
            assert.deepEqual(data.displays, [{ x: 0, y: 0, width: 1280, height: 720, scale: 1 }]);
            appendFileSync(join(evidence, 'diagnostics.jsonl'), text + '\n');
            return data;
        }
        async function opened(name) {
            const s = await check(`${name} open in mapped ${snapshot().orientation} geometry`, s => s.popup.name === name && s.popup.phase === 'open' && s.card && s.card.y === s.popup.popupCardFrame.currentRect.y);
            geometry(s, name);
            const diagnostic = diagnostics();
            assert.equal(diagnostic.popup.name, name);
            assert.equal(diagnostic.popup.phase, 'open');
            assert.deepEqual(diagnostic.popup.card, s.card);
            return s;
        }
        if (refactor) state = await check('refactor initially closed without resource polling', s => !s.popup.open && !s.resources.open);
        if (replay.fixture === 'all' || replay.fixture === 'popouts') {
            enterPhase('popouts');
            for (const orientation of ['horizontal', 'vertical']) {
                ipc('external', orientation);
                state = snapshot();
                click(state.buttons.clock);
                state = await opened('clock');
                if (orientation === 'horizontal') {
                    await screenshot('clock-setup');
                    click(state.calendar.url);
                    input('type', 'https://fixture.invalid/dav');
                    click(state.calendar.user);
                    input('type', 'fixture-user');
                    key('Tab');
                    input('type', 'fixture-only');
                    state = await check('calendar draft edits stay in the service until save', s => s.calendar.draftsEdited && s.calendar.saves === 0);
                    click(state.calendar.save);
                    await check('calendar save receives literal drafts and renders empty agenda', s => s.calendar.saves === 1 && s.calendar.savedDrafts && hasText(s.text, 'keine termine'));
                    await screenshot('clock-empty');
                    ipc('external', 'events');
                    await check('external calendar events render while open without another save', s => s.calendar.todayItems === 1 && hasText(s.text, 'fixture appointment') && s.calendar.saves === 1);
                }
                await screenshot(`clock-${orientation}`);
                state = snapshot();
                let opens = state.resources.opens;
                click(state.buttons.system);
                state = await opened('system');
                assert.equal(state.resources.open, true);
                assert.equal(state.resources.opens, opens + 1);
                if (orientation === 'horizontal') {
                    assert(hasText(state.text, 'fixture-host'));
                    assert(hasText(state.text, 'fixture service failed'));
                    assert(hasText(state.text, 'fixture-worker · 25%'));
                    await check('process line is visible above current issues', s => {
                        const process = s.text.find(row => row.text === 'fixture-worker · 25%')?.rect;
                        const issues = s.text.find(row => row.text === 'needs attention')?.rect;
                        return within(process, s.width, s.height) && issues && process.y + process.height <= issues.y;
                    });
                    await screenshot('system-populated');
                    click(state.resources.action);
                    state = await check('resource issue action routes once with the issue id', s => s.resources.actions === 1 && s.resources.lastAction === 'fixture-unit');
                    key('Tab');
                    await check('Tab reaches the agent action after open', s => s.resources.agentFocused);
                    key('shift+Tab');
                    key('Tab');
                    key('Return');
                    state = await check('Enter dispatches one agent request and disables duplicates', s => s.resources.agentPending && s.resources.agentActions === 1 && s.resources.agentIssueId === 'fixture-unit');
                    click(state.resources.agent);
                    await check('pending pointer does not dispatch twice', s => s.resources.agentActions === 1);
                    ipc('external', 'agent-failure');
                    state = await check('missing executable leaves visible failure and retry', s => !s.resources.agentPending && hasText(s.text, 'pi or wezterm unavailable in session path'));
                    await screenshot('system-agent-unavailable');
                    click(state.resources.agent);
                    await check('pointer retries the agent request once', s => s.resources.agentActions === 2 && s.resources.agentPending);
                    ipc('external', 'agent-success');
                    state = await check('agent dispatch closes overlay and releases polling', s => !s.popup.open && !s.resources.open && s.popup.lastCloseReason === 'agent-dispatched');
                    click(state.buttons.system);
                    state = await opened('system');
                    opens++;
                    ipc('external', 'resource-incidents');
                    state = await check('battery and updates omit agent actions while crash retains one', s => !!s.resources.crashAgent && !s.resources.batteryAgent && !s.resources.updatesAgent && !!s.resources.updatesOpen);
                    await check('crash and oom history is neutral while current failures retain severity', s => {
                        const history = s.resources.issueRows.filter(row => row.historical);
                        const service = s.resources.issueRows.find(row => row.id === 'fixture-unit');
                        const battery = s.resources.issueRows.find(row => row.id === 'battery-health');
                        return hasText(s.text, 'incident history') && history.length === 2 && history.every(row => row.clock && !row.attention && !row.destructive && !row.highlighted) && service?.destructive && battery?.attention;
                    });
                    // The process line moves later rows below the viewport.
                    // Reach them through real keyboard traversal and focus reveal.
                    for (let attempts = 0; attempts < 10 && !snapshot().resources.updatesFocused; attempts++) key('Tab');
                    state = await check('keyboard reveals the update action below the process line', s => s.resources.updatesFocused && s.resources.updatesOpen.y + s.resources.updatesOpen.height <= s.panel.y + s.panel.height);
                    click(state.resources.updatesOpen);
                    await check('updates retain their existing open action', s => s.resources.actions === 2 && s.resources.lastAction === 'updates');
                    await screenshot('system-agent-eligibility');
                    // Tab skips non-actionable battery wear; then coverage and event open.
                    key('Tab');
                    key('Tab');
                    key('Tab');
                    key('space');
                    state = await check('Space targets one crash group with all three occurrences visible', s => s.resources.agentPending && s.resources.agentIssueId === s.resources.crashGroupId && hasText(s.text, '3 crashes · latest sigsegv · pid 123') && hasText(s.text, '4 recorded'));
                    const crashGroupId = state.resources.crashGroupId;
                    ipc('external', 'agent-failure');
                    await screenshot('system-grouped-crashes');
                    key('shift+Tab');
                    key('Tab');
                    key('Return');
                    state = await check('Enter retries the eligible historical agent action', s => s.resources.agentPending && s.resources.agentIssueId === crashGroupId);
                    ipc('external', 'resource-more-crashes');
                    state = await check('new occurrence updates count and latest time without changing group or pending state', s => s.resources.agentPending && s.resources.agentIssueId === crashGroupId && s.resources.crashGroupId === crashGroupId && hasText(s.text, '4 crashes · latest sigsegv · pid 200') && hasText(s.text, '5 recorded') && s.text.some(row => row.text.startsWith('latest · just now')));
                    const groupRequests = state.resources.agentActions;
                    click(state.resources.crashAgent);
                    await check('updated pending group still suppresses duplicate pointer dispatch', s => s.resources.agentActions === groupRequests);
                    ipc('external', 'agent-failure');
                    click(state.resources.crashOpen);
                    await check('group open routes the stable id to its latest event', s => s.resources.actions === 3 && s.resources.lastAction === crashGroupId);
                    click(state.resources.crashAgent);
                    await check('pointer retries the updated crash group', s => s.resources.agentActions === groupRequests + 1 && s.resources.agentIssueId === crashGroupId);
                    ipc('external', 'agent-failure');
                    await screenshot('system-incidents');
                    key('Tab');
                    key('Tab');
                    await screenshot('system-long-incident');
                    ipc('external', 'resource-history-unavailable');
                    await check('incomplete history retains recorded incidents', s => !!s.resources.crashAgent && hasText(s.text, 'incident history unavailable or incomplete'));
                    ipc('external', 'empty-resources');
                    await check('external empty issues and processes render all-clear', s => hasText(s.text, 'disk · memory · battery · updates ok') && !hasText(s.text, 'fixture service failed') && !hasText(s.text, 'fixture-worker · 25%') && !s.resources.action && s.resources.actions === 3);
                }
                await screenshot(`system-empty-${orientation}`);
                click(state.buttons.clock);
                state = await opened('clock');
                assert.equal(state.resources.open, false, 'switching away must release resource gate');
                assert.equal(state.resources.opens, state.resources.closes);
                click(state.buttons.system);
                state = await opened('system');
                assert.equal(state.resources.opens, opens + 2);
                key('Escape');
                state = await check('Escape closes popup and releases resource lifecycle gate', s => !s.popup.open && !s.resources.open && s.popup.lastCloseReason === 'escape-key');
                assert.equal(state.resources.opens, state.resources.closes);
                click(state.buttons.system);
                state = await opened('system');
                assert.equal(state.resources.opens, opens + 3);
                click({ x: 1200, y: 680, width: 10, height: 10 });
                state = await check('outside pointer closes reopened system and releases gate', s => !s.popup.open && !s.resources.open && s.popup.lastCloseReason === 'outside');
                assert.equal(state.resources.opens, state.resources.closes);
                const refreshes = state.calendar.refreshes;
                click(state.buttons.clock);
                state = await opened('clock');
                assert.equal(state.calendar.refreshes, refreshes + 1);
                assert(hasText(state.text, 'fixture appointment'));
                click(state.buttons.clock);
                await check('bar pointer closes reopened clock', s => !s.popup.open && s.popup.lastCloseReason === 'bar-toggle');
            }
        }
        if (replay.fixture === 'all' || replay.fixture === 'privilege') {
            enterPhase('privilege');
            ipc('external', 'privilege');
            state = await check('privilege overlay maps and focuses literal input', s => s.auth.visible && s.auth.active && s.auth.inputFocused);
            assert(within(state.auth.card, state.width, state.height));
            assert(state.auth.shake.height > 0);
            assert.equal(state.auth.shake.height, state.auth.shakeContentHeight);
            assert.equal(state.auth.shake.width, state.auth.card.width - 44);
            await screenshot('privilege-initial');
            key('Tab');
            await check('Tab reaches password reveal without focus theft', s => s.auth.revealFocused && !s.auth.inputFocused);
            key('Tab');
            await check('Tab reaches authentication cancel', s => s.auth.cancelFocused);
            key('Tab');
            await check('Tab reaches authentication authorize', s => s.auth.authorizeFocused);
            key('Tab');
            await check('authentication traversal wraps to input', s => s.auth.inputFocused);
            key('shift+Tab');
            await check('reverse Tab reaches authorize', s => s.auth.authorizeFocused);
            key('shift+Tab');
            await check('reverse Tab reaches cancel', s => s.auth.cancelFocused);
            key('shift+Tab');
            await check('reverse Tab reaches reveal', s => s.auth.revealFocused);
            key('shift+Tab');
            await check('reverse Tab returns to input', s => s.auth.inputFocused);
            click(state.auth.input);
            input('type', 'fixture-only');
            await check('privilege input is present and masked without serializing its text', s => s.auth.hasInput && s.auth.inputMasked);
            await screenshot('privilege-masked-input');
            key('Return');
            state = await check('privilege Enter routes literal submit and renders rejection', s => s.auth.submissions === 1 && s.auth.receivedLiteral && s.auth.active && hasText(s.auth.text, 'password rejected') && hasText(s.auth.text, 'fixture input'));
            const failureCard = state.auth.card;
            const failureHeight = state.auth.shake.height;
            for (let i = 0; i < 4; i++) {
                state = await check(`failure shake keeps supplied frame size ${i}`, s => s.auth.shake.height === s.auth.shakeContentHeight && s.auth.shake.height === failureHeight);
                assert.deepEqual(state.auth.card, failureCard);
                assert.equal(state.auth.shake.width, failureCard.width - 44);
                await delay(60);
            }
            await screenshot('privilege-rejected');
            click(state.auth.reveal);
            state = await check('pointer reveal toggles without stealing input focus', s => s.auth.revealed && s.auth.revealFocused && !s.auth.inputFocused && hasText(s.auth.text, 'fixture input'));
            // Reveal is captured with an empty field, never even a synthetic secret.
            await screenshot('privilege-reveal-empty');
            click(state.auth.reveal);
            await check('pointer hides input again', s => !s.auth.revealed);
            click(state.auth.input);
            input('type', 'fixture-only');
            key('Return');
            state = await check('second Enter renders fixture authorization success', s => s.auth.submissions === 2 && s.auth.receivedLiteral && s.auth.confirming && !s.auth.active && hasText(s.auth.text, 'authorized'));
            await screenshot('privilege-authorized');
            ipc('external', 'privilege');
            state = await check('new fixture request reacquires focus', s => s.auth.active && s.auth.inputFocused && !s.auth.confirming);
            click(state.auth.cancel);
            await check('pointer cancel dismisses privilege overlay', s => !s.auth.visible && s.auth.cancellations === 1);
            ipc('external', 'privilege');
            state = await check('privilege reopens after pointer cancellation', s => s.auth.visible && s.auth.inputFocused);
            click(state.auth.input);
            input('type', 'fixture-only');
            key('Escape');
            await check('Escape cancels privilege without submitting', s => !s.auth.visible && s.auth.cancellations === 2 && s.auth.submissions === 2);
        }
        if (replay.fixture === 'all' || replay.fixture === 'noko') {
            enterPhase('noko');
            ipc('external', 'horizontal');
            for (const phase of ['capturing', 'transcribing', 'finished']) {
                ipc('external', phase);
                state = await check(`Noko renders ${phase}`, s => s.noko.visible && s.noko.phase === phase && hasText(s.noko.text, phase));
                assert(within(state.noko.tab, state.width, state.height));
                assert.equal(state.noko.tab.y, 66);
                assert.equal(state.noko.tab.x, Math.round((state.width - state.noko.tab.width) / 2));
                if (phase !== 'finished') await check(`Noko ${phase} animation ticks`, s => s.noko.tick > state.noko.tick);
                await screenshot(`noko-${phase}`);
            }
            const finishedTick = state.noko.tick;
            await delay(180);
            assert.equal(snapshot().noko.tick, finishedTick);
            ipc('external', 'vertical');
            state = await check('Noko geometry reacts to vertical bar', s => s.noko.tab.y === 18 && s.noko.tab.x === Math.round(48 + (s.width - 48 - s.noko.tab.width) / 2));
            await screenshot('noko-vertical');
            ipc('external', 'thicker-bar');
            await check('Noko geometry reacts to bar thickness without remount', s => s.noko.tab.x === Math.round(64 + (s.width - 64 - s.noko.tab.width) / 2));
            await screenshot('noko-thicker-bar');
            ipc('external', 'idle');
            await check('Noko idle removes overlay', s => !s.noko.visible && s.noko.phase === '');
        }
        if (refactor) {
            const diagnostic = diagnostics();
            const transitions = await waitFor('transition log reaches diagnostic operation', () => {
                const log = readFileSync(fixtureLog, 'utf8');
                const events = log.split('\n').filter(line => line.includes('"kind":"popup-transition"')).map(line => JSON.parse(line.slice(line.indexOf('{'))));
                return diagnostic.popup.operationId === 0 || events.at(-1)?.operationId === diagnostic.popup.operationId ? events : null;
            });
            assert(!JSON.stringify(transitions).includes('diagnostic-private-sentinel'));
            if (replay.fixture === 'all' || replay.fixture === 'popouts') {
                assert.deepEqual(transitions.slice(0, 4).map(({ operationId, event }) => [operationId, event]), [[1, 'opening'], [1, 'opened'], [2, 'switched'], [3, 'closed']]);
                assert.equal(transitions.at(-1).event, 'closed');
                assert.equal(transitions.at(-1).operationId, diagnostic.popup.operationId);
                for (let i = 1; i < transitions.length; i++) {
                    const event = transitions[i];
                    const previous = transitions[i - 1];
                    assert.equal(event.operationId, previous.operationId + (event.event === 'opened' ? 0 : 1));
                    if (event.event === 'opened') assert.equal(previous.event, 'opening');
                }
                console.log('pass: redacted diagnostics correlate with runtime popup transitions');
            } else assert.equal(transitions.length, 0);
            console.log('refactor UI assertions passed; screenshots require visual review');
        }
        if (['all', 'popouts', 'system'].includes(replay.fixture)) {
            await stopChild(fixtureProcess);
            assertCleanLog(readFileSync(fixtureLog, 'utf8'));
            children.splice(children.indexOf(fixtureProcess), 1);
            latestSnapshot = null;
            enterPhase('system-boundaries');
            await runResourceProbe({original: nativeResources, env, start, stop: stopChild, waitFor, ipc, screenshot, snapshot,
                setFixture(path, log, child) { fixture = path; fixtureLog = log; fixtureProcess = child; }
            });
        }
        if (tasks) {
            if (refactor) {
                await stopChild(fixtureProcess);
                assertCleanLog(readFileSync(fixtureLog, 'utf8'));
                children.splice(children.indexOf(fixtureProcess), 1);
                fixtureLog = '';
                latestSnapshot = null;
                prepareTasks();
                fixture = '/project/tasks-ui-test.qml';
                fixtureProcess = start('qs', ['--no-color', '-p', fixture], 'tasks');
                fixtureLog = join(evidence, 'tasks.log');
                await waitFor('task fixture loaded', () => readFileSync(fixtureLog, 'utf8').includes('Configuration Loaded'));
            }
            enterPhase(taskSelection);
            await runTasks({ ipc, snapshot, input, key, click, check, screenshot, run, delay });
        }
        if (integration) {
            if (refactor) {
                await stopChild(fixtureProcess);
                assertCleanLog(readFileSync(fixtureLog, 'utf8'));
                children.splice(children.indexOf(fixtureProcess), 1);
                fixtureLog = '';
                latestSnapshot = null;
                for (const [target, text] of taskOriginals)
                    writeFileSync(`/project/${target}`, text);
                await prepareIntegration();
                fixture = '/project/niri-integration-ui-test.qml';
                fixtureProcess = start('qs', ['--no-color', '-p', fixture], 'niri-integration');
                fixtureLog = join(evidence, 'niri-integration.log');
                await waitFor('niri integration fixture loaded', () => readFileSync(fixtureLog, 'utf8').includes('Configuration Loaded'));
            }
            enterPhase('niri');
            expectedWriteFailure = await runNiriIntegration({
                run, start, check, snapshot, ipc, screenshot, click, input, key, waitFor, evidence, runtime,
                modeFile: integrationSetup.modeFile,
                compositorKey: async name => {
                    childEnvironment(runtime, ...endpoints);
                    assert.equal(compositor.exitCode, null);
                    assert.equal(compositor.signalCode, null);
                    const keys = { Escape: [1], Return: [28], 'ctrl+c': [29, 46] }[name];
                    assert(keys, 'unsupported nested compositor key');
                    for (const [key, pressed] of [...keys.map(key => [key, 1]), ...keys.toReversed().map(key => [key, 0])]) {
                        const command = Buffer.alloc(12);
                        [compositor.pid, key, pressed].forEach((value, index) => command.writeUInt32LE(value, index * 4));
                        writeFileSync(join(runtime, 'niri-integration-keyboard.fifo'), command);
                        appendFileSync(join(evidence, 'input.log'), `nested compositor ${compositor.pid}: key ${key} state ${pressed}\n`);
                        await delay(50);
                    }
                },
                stop: async child => {
                    await stopChild(child);
                    children.splice(children.indexOf(child), 1);
                }
            });
        }
        enterPhase('runtime-diagnostics');
    } catch (error) {
        failure = error;
    } finally {
        await finishUiRun(failure, {
            children,
            checkLogs: () => {
                let diagnostics;
                for (const log of runtimeLogs) {
                    const text = readFileSync(log, 'utf8');
                    if (log === join(evidence, 'niri.log')) diagnostics = assertNiriLog(text, expectedWriteFailure);
                    else assertCleanLog(text);
                }
                writeFileSync(join(evidence, 'runtime-diagnostics.json'), JSON.stringify({ ...diagnostics, expectedWriteFailure }, null, 2));
            },
            capture: error => captureUiFailure(error, {
                evidence, fixture: replay.fixture, phase, snapshot: latestSnapshot, screenshot: failureScreenshot
            })
        });
    }
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
    try {
        if (process.argv[2] === '--inside') await inside(replayRequest(process.argv.slice(3)));
        else await outer(replayRequest(process.argv.slice(2)));
    } catch (error) {
        console.error(error.stack);
        process.exitCode = 1;
    }
}