// /AIWK_COMMON/modules/dev/test_launcher.js
// AIWK EXT4 v036
(function () {
    "use strict";

    const AIWK = window.AIWK;

    function el(tag, className, text) {
        const node = document.createElement(tag);
        if (className) {
            node.className = className;
        }
        if (text !== undefined) {
            node.textContent = text;
        }
        return node;
    }

    function safeJson(obj) {
        return JSON.stringify(obj, null, 2);
    }

    function nowIso() {
        return new Date().toISOString();
    }

    function makePrint(status, testId, name, result, detail, extra) {
        const lines = [];
        lines.push("PRINT: " + status);
        lines.push("TEST: " + testId);
        lines.push("NAME: " + name);
        lines.push("RESULT: " + result);
        if (detail) {
            lines.push("DETAIL: " + detail);
        }
        if (extra) {
            Object.keys(extra).forEach(function (key) {
                const val = extra[key];
                if (typeof val === "object") {
                    lines.push(key + ": " + safeJson(val));
                } else {
                    lines.push(key + ": " + String(val));
                }
            });
        }
        return lines.join("\n");
    }

    function createWinOnce(cfg) {
        const old = AIWK.win.get(cfg.id);
        if (old) {
            old.bringFront();
            return old;
        }
        const win = AIWK.win.create(cfg);
        try {
            AIWK.group.addWin("zone-main", cfg.id);
        } catch (err) {
            // group may not be ready in early boot. Do not stop UI test launcher.
        }
        return win;
    }

    AIWK.modules.register("dev.test_launcher", {
        name: "dev.test_launcher",
        run: async function (ctx) {
            return {
                ok: 1,
                module: "dev.test_launcher",
                win_id: ctx.win.id,
                count: AIWK.win.list().length,
                wins: AIWK.win.list(),
                received: ctx.msg || {},
                message: "dev.test_launcher run completed"
            };
        },
        mount: function (ctx) {
            const box = el("div", "space-y-2 text-sm aiwk-test-launcher select-text");
            const title = el("div", "text-base font-semibold select-text", "v036 통신 테스트 런처");
            const desc = el("div", "text-xs select-text", "기존 직접 버튼은 유지하되, v030부터 수동 테스트 관리창에서 JSON 항목별 완료/휴지통 상태를 관리합니다.");

            const actions = el("div", "flex flex-wrap gap-1 select-text");
            function btn(label, action) {
                const b = document.createElement("button");
                b.type = "button";
                b.className = "aiwk-btn";
                b.dataset.launcherAction = action;
                b.textContent = label;
                return b;
            }

            actions.appendChild(btn("TEST 1 부팅/목록", "test_boot_list"));
            actions.appendChild(btn("TEST 2 창 3개", "test_open_all"));
            actions.appendChild(btn("TEST 3 win.run", "test_win_run"));
            actions.appendChild(btn("TEST 4 zone.run", "test_zone_run"));
            actions.appendChild(btn("TEST 5 token fetch", "test_token_fetch"));
            actions.appendChild(btn("TEST 6 token login", "test_token_login"));
            actions.appendChild(btn("TEST 7 site profile run", "test_site_profile"));
            actions.appendChild(btn("TEST 8 expected 오류", "test_expected_errors"));
            actions.appendChild(btn("TEST 9 profile 저장", "test_site_profile_save"));
            actions.appendChild(btn("TEST 10 BUS 대화", "test_bus_dialog"));
            actions.appendChild(btn("TEST 11 창 목록", "test_window_catalog"));
            actions.appendChild(btn("TEST 12 전체 창 selftest", "test_all_window_selftest"));
            actions.appendChild(btn("TEST 13 JS Runner", "test_js_runner"));
            actions.appendChild(btn("TEST 14 통신 3창", "test_comm_three_windows"));
            actions.appendChild(btn("BUS창", "open_bus_test"));
            actions.appendChild(btn("WSS창", "open_wss_test"));
            actions.appendChild(btn("NR창", "open_nr_test"));
            actions.appendChild(btn("전체 TEST", "test_all"));
            actions.appendChild(btn("결과 복사", "copy_result"));
            actions.appendChild(btn("결과 지우기", "clear_result"));
            actions.appendChild(btn("hello 창", "open_hello"));
            actions.appendChild(btn("auth 창", "open_auth"));
            actions.appendChild(btn("registry 창", "open_registry"));
            actions.appendChild(btn("zone 창", "open_zone"));
            actions.appendChild(btn("site profile 창", "open_site_profile"));
            actions.appendChild(btn("JS Runner 창", "open_js_runner"));
            actions.appendChild(btn("완성모듈 목록", "show_completed_modules"));
            actions.appendChild(btn("수동테스트 관리", "open_manual_test_manager"));

            const resultBox = el("pre", "aiwk-run-result select-text", "");
            resultBox.id = "aiwk-result-log";
            resultBox.textContent = window.AIWK_LOG && window.AIWK_LOG.getResult ? window.AIWK_LOG.getResult() : "TEST 버튼을 누르십시오.";
            const dialogBox = el("pre", "aiwk-run-result select-text", "");
            dialogBox.id = "aiwk-dialog-log";
            dialogBox.textContent = window.AIWK_LOG && window.AIWK_LOG.getDialog ? window.AIWK_LOG.getDialog() : "";
            const errorBox = el("div", "aiwk-auth-msg select-text", "");
            const checks = el("div", "aiwk-checkbox select-text", "");
            checks.innerHTML = "<b>v018 검토 항목</b><br>" +
                "1. TEST 1~8이 PRINT 형식으로 나오는지 확인<br>" +
                "2. PRINT: 오류는 원인/해결/STOP:true 포함 여부 확인<br>" +
                "3. expected 오류는 Chrome 확장 오류 목록에 남기지 않음<br>" +
                "4. token login 성공 시 role=dev 확인<br>" +
                "5. TEST 9에서 Site Profile 저장/불러오기 성공 확인<br>" +
                "6. TEST 10에서 너의 기능은?/너의 사용법은?/셀프테스트 해라! BUS 대화 확인<br>" +
                "7. 모든 텍스트 드래그 선택/복사 가능 확인<br>" +
                "8. TEST 11~13에서 창 목록/전체 selftest/JS Runner 확인<br>" +
                "9. JS 버튼 클릭 시 JS Runner가 열리고 dev 권한에서 BUS 실행 가능 확인";

            function setText(text) {
                const v = String(text || "");
                const old = String(resultBox.textContent || "").trim();
                resultBox.textContent = old && old !== "TEST 버튼을 누르십시오." ? old + "\n\n" + v : v;
                resultBox.scrollTop = resultBox.scrollHeight;
                if (window.AIWK_LOG && typeof window.AIWK_LOG.appendResult === "function") {
                    window.AIWK_LOG.appendResult("LAUNCHER PRINT", v);
                    resultBox.textContent = window.AIWK_LOG.getResult ? window.AIWK_LOG.getResult() : resultBox.textContent;
                }
                ctx.status.textContent = "print ready";
            }

            function printOk(testId, name, detail, extra) {
                setText(makePrint("정상", testId, name, "OK", detail, Object.assign({ NEXT: "계속 진행 가능" }, extra || {})));
            }

            function printError(testId, name, code, cause, fix, extra) {
                setText(makePrint("오류", testId, name, "ERROR", cause, Object.assign({ ERROR_CODE: code, FIX: fix, STOP: true }, extra || {})));
                ctx.status.textContent = "stopped";
            }

            function clearError() {
                errorBox.textContent = "";
                errorBox.className = "aiwk-auth-msg select-text";
            }

            function openHello() {
                return createWinOnce({
                    id: "hello-win",
                    title: AIWK.i18n.t("app.title", "AIWK EXT4 v023"),
                    module: "sample.hello",
                    rect: { x: 590, y: 70, w: 440, h: 360 }
                });
            }

            function openAuth() {
                return createWinOnce({
                    id: "auth-win",
                    title: "Auth / Role",
                    module: "auth.panel",
                    rect: { x: 590, y: 450, w: 480, h: 420 }
                });
            }

            function openRegistry() {
                return createWinOnce({
                    id: "registry-win",
                    title: "Win Registry v027",
                    module: "win.registry",
                    rect: { x: 40, y: 520, w: 620, h: 430 }
                });
            }

            function openZoneRegistry() {
                return createWinOnce({
                    id: "zone-registry-win",
                    title: "Zone Registry v018",
                    module: "zone.registry",
                    rect: { x: 690, y: 520, w: 650, h: 450 }
                });
            }

            function openSiteProfile() {
                return createWinOnce({
                    id: "site-profile-win",
                    title: "Site Profile v018",
                    module: "site.profile_registry",
                    rect: { x: 80, y: 120, w: 660, h: 560 }
                });
            }

            function openJsRunner() {
                return createWinOnce({
                    id: "js-runner-win",
                    title: "JS Runner v018",
                    module: "dev.js_runner",
                    rect: { x: 120, y: 120, w: 720, h: 620 }
                });
            }

            function openManualTestManager() {
                return createWinOnce({
                    id: "manual-test-manager-win",
                    title: "수동 테스트 관리 " + (AIWK.version || "v036"),
                    module: "dev.manual_test_manager",
                    rect: { x: 70, y: 90, w: 760, h: 650 }
                });
            }


            function openBusTest() {
                return createWinOnce({ id: "bus-test-win", title: "BUS 통신 테스트창 v027", module: "comm.bus_test", rect: { x: 40, y: 90, w: 560, h: 520 } });
            }
            function openWssTest() {
                return createWinOnce({ id: "wss-test-win", title: "WSS 통신 테스트창 v027", module: "comm.wss_test", rect: { x: 620, y: 90, w: 580, h: 560 } });
            }
            function openNrTest() {
                return createWinOnce({ id: "nr-test-win", title: "Node-RED 통신 테스트창 v027", module: "comm.nr_test", rect: { x: 1220, y: 90, w: 580, h: 520 } });
            }
            function openCommThree() {
                openBusTest();
                openWssTest();
                openNrTest();
            }

            function openAllBase() {
                openHello();
                openAuth();
                openRegistry();
            }

            function ensureDemoZone() {
                openHello();
                openAuth();
                if (!AIWK.zone || typeof AIWK.zone.create !== "function") {
                    throw new Error("ZONE_ENGINE_MISSING: AIWK.zone.create is missing.");
                }
                const zone = AIWK.zone.create({ id: "zone-demo", title: "Demo Zone", order: 1, useOrder: 1 });
                zone.addWin("hello-win");
                zone.addWin("auth-win");
                return zone;
            }

            async function testBootList() {
                if (!AIWK.win || typeof AIWK.win.list !== "function") {
                    throw new Error("WIN_ENGINE_MISSING: AIWK.win.list is missing.");
                }
                printOk("v027-1", "boot and win list", "AIWK.win.list 실행 성공", { WINS: AIWK.win.list() });
            }

            async function testOpenAll() {
                openAllBase();
                printOk("v027-2", "open base windows", "hello/auth/registry 창 생성 또는 앞으로 가져오기 완료", { WINS: AIWK.win.list() });
            }

            async function testWinRun() {
                openAllBase();
                let hello;
                let auth;
                if (window.AIWK_BUS && typeof window.AIWK_BUS.ask === "function") {
                    hello = await window.AIWK_BUS.ask("hello-win", "실행해라!", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                    auth = await window.AIWK_BUS.ask("auth-win", "실행해라!", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                } else {
                    hello = await AIWK.win.run("hello-win", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                    auth = await AIWK.win.run("auth-win", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                }
                printOk("v027-3", "BUS win.run", "hello-win/auth-win BUS 실행 성공", { HELLO: hello, AUTH: auth });
            }

            async function testZoneRun() {
                ensureDemoZone();
                openZoneRegistry();
                let res;
                if (window.AIWK_BUS && typeof window.AIWK_BUS.runZone === "function") {
                    window.AIWK_BUS.addZone("zone-demo-bus", { title: "Demo BUS Zone", links: ["link-test-hello-selftest", "link-test-auth-selftest"] });
                    window.AIWK_BUS.addLink({ id: "link-test-hello-selftest", zone_id: "zone-demo-bus", from: "test-launcher-win", to: "hello-win", label: "셀프테스트 해라!", action: "selftest", order: 10 });
                    window.AIWK_BUS.addLink({ id: "link-test-auth-selftest", zone_id: "zone-demo-bus", from: "test-launcher-win", to: "auth-win", label: "셀프테스트 해라!", action: "selftest", order: 20 });
                    res = await window.AIWK_BUS.runZone("zone-demo-bus", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                } else {
                    res = await AIWK.zone.run("zone-demo", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                }
                printOk("v027-4", "BUS zone.run", "zone-demo BUS 순차 실행 성공", { ZONE_RESULT: res });
            }

            async function testTokenFetch() {
                if (!AIWK.auth || typeof AIWK.auth.fetchRemoteToken !== "function") {
                    throw new Error("AUTH_REMOTE_MISSING: AIWK.auth.fetchRemoteToken is missing.");
                }
                const token = await AIWK.auth.fetchRemoteToken();
                if (!AIWK.auth.isHexToken(token)) {
                    throw new Error("REMOTE_TOKEN_FORMAT_INVALID: token.txt 값이 64자리 hex가 아닙니다.");
                }
                printOk("v027-5", "remote token fetch via BUS", "service_worker TAB BUS로 token.txt 읽기 성공", { TOKEN_LEN: token.length, URL: AIWK.auth.remoteTokenUrl, BUS: "service_worker_tabbus" });
            }

            async function testTokenLogin() {
                const token = await AIWK.auth.fetchRemoteToken();
                const parsed = await AIWK.auth.loginRemote(token);
                if (AIWK.auth.role !== "dev") {
                    throw new Error("DEV_ROLE_NOT_APPLIED: remote token login 후 role이 dev가 아닙니다.");
                }
                printOk("v027-6", "remote token login via BUS", "service_worker TAB BUS token으로 dev 로그인 성공", { ROLE: AIWK.auth.role, PARSED: parsed, BUS: "service_worker_tabbus" });
            }

            async function testSiteProfile() {
                openSiteProfile();
                const res = await AIWK.win.run("site-profile-win", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                printOk("v027-7", "site profile run", "site.profile_registry run 성공", { SITE_PROFILE: res });
            }

            async function testSiteProfileSave() {
                openSiteProfile();
                if (!AIWK.auth.can("dev")) {
                    const token = await AIWK.auth.fetchRemoteToken();
                    await AIWK.auth.loginRemote(token);
                }
                if (!AIWK.auth.can("dev")) {
                    throw new Error("DEV_ROLE_REQUIRED: TEST 9 requires dev role after remote token login.");
                }
                const res = AIWK.siteProfile.testRegisterCurrentSite();
                printOk("v027-9", "site profile save/load", "현재 도메인 Site Profile 저장/불러오기 검증 성공", { SITE_PROFILE_SAVE: res, ROLE: AIWK.auth.role });
            }

            async function testBusDialog() {
                openAllBase();
                if (!window.AIWK_BUS || typeof window.AIWK_BUS.ask !== "function") {
                    throw new Error("AIWK_BUS_MISSING: aiwk_bus_graph.js 로드가 필요합니다.");
                }
                const fn = await window.AIWK_BUS.ask("auth-win", "너의 기능은?", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                const hp = await window.AIWK_BUS.ask("auth-win", "너의 사용법은?", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                const st = await window.AIWK_BUS.ask("auth-win", "셀프테스트 해라!", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                if (!fn.ok || !hp.ok || !st.ok) {
                    throw new Error("BUS_DIALOG_FAIL: auth-win BUS 대화 중 실패가 있습니다.");
                }
                printOk("v027-10", "BUS dialog", "너의 기능은?/너의 사용법은?/셀프테스트 해라! 정상", { WHATCANIDO: fn, HELP: hp, SELFTEST: st, COMPLETED: window.AIWK_BUS.completedModules() });
            }

            async function testWindowCatalog() {
                openAllBase();
                openSiteProfile();
                openJsRunner();
                const wins = AIWK.win.list();
                const modules = window.AIWK_BUS && window.AIWK_BUS.modules ? Object.keys(window.AIWK_BUS.modules).sort() : [];
                printOk("v027-11", "window catalog", "현재 열린 창과 BUS 등록 모듈 목록 확인", { WINS: wins, BUS_MODULES: modules, COUNT: wins.length });
            }

            async function testAllWindowSelftest() {
                openAllBase();
                openSiteProfile();
                openJsRunner();
                if (!window.AIWK_BUS || typeof window.AIWK_BUS.ask !== "function") {
                    throw new Error("AIWK_BUS_MISSING: aiwk_bus_graph.js 로드가 필요합니다.");
                }
                const wins = AIWK.win.list();
                const results = [];
                for (let i = 0; i < wins.length; i += 1) {
                    const id = wins[i].id;
                    const res = await window.AIWK_BUS.ask(id, "셀프테스트 해라!", { source: "dev.test_launcher", check: "v018", at: nowIso(), index: i });
                    results.push({ win_id: id, result: res });
                    if (!res.ok) {
                        throw new Error("WINDOW_SELFTEST_FAIL: " + id + " / " + (res.message || res.error || "unknown"));
                    }
                }
                printOk("v027-12", "all window selftest", "열린 모든 창 selftest 완료. 통과 창은 완성모듈 처리", { RESULTS: results, COMPLETED: window.AIWK_BUS.completedModules() });
            }

            async function testJsRunner() {
                openJsRunner();
                const res = await AIWK.win.run("js-runner-win", { source: "dev.test_launcher", check: "v018", at: nowIso() });
                printOk("v027-13", "js runner", "JS Runner 창 생성/run 확인", { JS_RUNNER: res, ROLE: AIWK.auth.role });
            }

            async function testExpectedErrors() {
                let winError = "";
                let zoneError = "";
                try {
                    await AIWK.win.run("missing-win", {});
                    throw new Error("MISSING_WIN_DID_NOT_FAIL: missing-win이 오류를 내지 않았습니다.");
                } catch (errWin) {
                    winError = errWin.message;
                }
                try {
                    await AIWK.zone.run("missing-zone", {});
                    throw new Error("MISSING_ZONE_DID_NOT_FAIL: missing-zone이 오류를 내지 않았습니다.");
                } catch (errZone) {
                    zoneError = errZone.message;
                }
                printOk("v027-8", "expected errors", "없는 win/zone 오류를 화면 결과로만 검증 완료", { WIN_ERROR: winError, ZONE_ERROR: zoneError });
            }

            async function testCommThreeWindows() {
                openCommThree();
                const reg = AIWK.comm && AIWK.comm.tabRegister ? await AIWK.comm.tabRegister("tabbus") : { ok: 0, error: "AIWK.comm missing" };
                const list = AIWK.comm && AIWK.comm.tabList ? await AIWK.comm.tabList() : { ok: 0, error: "AIWK.comm missing" };
                printOk("v027-14", "BUS/WSS/NR communication windows", "BUS/WSS/Node-RED 3개 통신 테스트창 생성 및 Tab BUS 등록/목록 확인", { REGISTER: reg, CLIENTS: list, STATUS: AIWK.comm ? AIWK.comm.status() : {} });
            }

            async function runAll() {
                const logs = [];
                const tests = [
                    ["v027-1", "boot and win list", testBootList],
                    ["v027-2", "open base windows", testOpenAll],
                    ["v027-3", "win.run", testWinRun],
                    ["v027-4", "zone.run", testZoneRun],
                    ["v027-5", "remote token fetch", testTokenFetch],
                    ["v027-6", "remote token login", testTokenLogin],
                    ["v027-7", "site profile run", testSiteProfile],
                    ["v027-8", "expected errors", testExpectedErrors],
                    ["v027-9", "site profile save/load", testSiteProfileSave],
                    ["v027-10", "BUS dialog", testBusDialog],
                    ["v027-11", "window catalog", testWindowCatalog],
                    ["v027-12", "all window selftest", testAllWindowSelftest],
                    ["v027-13", "js runner", testJsRunner],
                    ["v027-14", "communication three windows", testCommThreeWindows]
                ];
                for (let i = 0; i < tests.length; i += 1) {
                    try {
                        await tests[i][2]();
                        logs.push("OK " + tests[i][0] + " " + tests[i][1]);
                    } catch (err) {
                        printError(tests[i][0], tests[i][1], "TEST_FAIL", err.message, "해당 테스트의 엔진/권한/service_worker TAB BUS/원격 URL/등록 id/Site Profile 저장소를 확인하십시오.", { PASSED_BEFORE_STOP: logs });
                        return;
                    }
                }
                printOk("v027-all", "all tests", "전체 TEST 통과", { PASSED: logs, ROLE: AIWK.auth.role, WINS: AIWK.win.list(), ZONES: AIWK.zone ? AIWK.zone.list() : [] });
            }

            actions.addEventListener("click", function (ev) {
                const b = ev.target.closest("button[data-launcher-action]");
                if (!b) {
                    return;
                }
                ev.preventDefault();
                ev.stopPropagation();
                const action = b.dataset.launcherAction;
                clearError();
                (async function () {
                    try {
                        if (action === "test_boot_list") {
                            await testBootList();
                        } else if (action === "test_open_all") {
                            await testOpenAll();
                        } else if (action === "test_win_run") {
                            await testWinRun();
                        } else if (action === "test_zone_run") {
                            await testZoneRun();
                        } else if (action === "test_token_fetch") {
                            await testTokenFetch();
                        } else if (action === "test_token_login") {
                            await testTokenLogin();
                        } else if (action === "test_site_profile") {
                            await testSiteProfile();
                        } else if (action === "test_expected_errors") {
                            await testExpectedErrors();
                        } else if (action === "test_site_profile_save") {
                            await testSiteProfileSave();
                        } else if (action === "test_bus_dialog") {
                            await testBusDialog();
                        } else if (action === "test_window_catalog") {
                            await testWindowCatalog();
                        } else if (action === "test_all_window_selftest") {
                            await testAllWindowSelftest();
                        } else if (action === "test_js_runner") {
                            await testJsRunner();
                        } else if (action === "test_comm_three_windows") {
                            await testCommThreeWindows();
                        } else if (action === "test_all") {
                            await runAll();
                        } else if (action === "copy_result") {
                            await navigator.clipboard.writeText(resultBox.textContent || "");
                            ctx.status.textContent = "copied";
                        } else if (action === "clear_result") {
                            if (window.AIWK_LOG && typeof window.AIWK_LOG.clear === "function") {
                                window.AIWK_LOG.clear();
                            }
                            resultBox.textContent = "";
                            dialogBox.textContent = "";
                            ctx.status.textContent = "cleared";
                        } else if (action === "open_hello") {
                            openHello();
                            printOk("manual-open-hello", "open hello", "hello-win 표시 완료");
                        } else if (action === "open_auth") {
                            openAuth();
                            printOk("manual-open-auth", "open auth", "auth-win 표시 완료");
                        } else if (action === "open_registry") {
                            openRegistry();
                            printOk("manual-open-registry", "open registry", "registry-win 표시 완료");
                        } else if (action === "open_zone") {
                            openZoneRegistry();
                            printOk("manual-open-zone", "open zone registry", "zone-registry-win 표시 완료");
                        } else if (action === "open_site_profile") {
                            openSiteProfile();
                            printOk("manual-open-site-profile", "open site profile", "site-profile-win 표시 완료");
                        } else if (action === "open_js_runner") {
                            openJsRunner();
                            printOk("manual-open-js-runner", "open js runner", "js-runner-win 표시 완료");
                        } else if (action === "open_bus_test") {
                            openBusTest();
                            printOk("manual-open-bus-test", "open BUS test", "bus-test-win 표시 완료");
                        } else if (action === "open_wss_test") {
                            openWssTest();
                            printOk("manual-open-wss-test", "open WSS test", "wss-test-win 표시 완료");
                        } else if (action === "open_nr_test") {
                            openNrTest();
                            printOk("manual-open-nr-test", "open Node-RED test", "nr-test-win 표시 완료");
                        } else if (action === "show_completed_modules") {
                            const completed = window.AIWK_BUS && typeof window.AIWK_BUS.completedModules === "function" ? window.AIWK_BUS.completedModules() : {};
                            printOk("manual-completed-modules", "completed modules", "완성모듈 상태 표시", { COMPLETED: completed });
                        } else if (action === "open_manual_test_manager") {
                            openManualTestManager();
                            printOk("manual-test-manager", "open manual test manager", "JSON 기반 수동 테스트 관리창 표시 완료");
                        } else {
                            throw new Error("UNKNOWN_ACTION: " + action);
                        }
                    } catch (err) {
                        printError("manual-" + action, action, "TEST_ACTION_FAIL", err.message, "버튼 action, module 등록, service_worker TAB BUS, host_permissions, token URL을 확인하십시오.");
                    }
                }());
            });

            box.appendChild(title);
            box.appendChild(desc);
            box.appendChild(actions);
            box.appendChild(errorBox);
            box.appendChild(resultBox);
            box.appendChild(el("div", "text-xs font-semibold select-text", "대화 로그"));
            box.appendChild(dialogBox);
            box.appendChild(checks);
            ctx.body.appendChild(box);
            ctx.status.textContent = "v027 communication test launcher ready";
        }
    });
}());
