import assert from "node:assert/strict"; import fs, { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; import vm from "node:vm"; import { createDesktopPrefs, createDesktopText, DESKTOP_NOTICES, DESKTOP_PORTS, jsonFile, registerDesktopPrefs } from "./desktop-prefs.mjs"; import { ONBOARDING_CLOUD_HINT, onboardingHints, WELCOME_VERSION } from "./onboarding-record.mjs"; const require = createRequire(import.meta.url); const environments = require("./environments.cjs"); const localOrigin = require("./local-origin.cjs"); const POPUP = "whats-new-2026-11", BOAT = "boat-credit-2026-11", GROK = "grok-move-2026-11", MODELGO = "modelgo-offer-2026-11"; const NOON = new Date(2026, 9, 16, 12).getTime(); const LOCAL = "http://127.0.0.1:18799", CLOUD = "https://omb-u-1.fly.dev", VPS = "https://bots.example.test"; test("a page on an older release keeps working with the longer list: its own ids record as before, and it never reads the new one", () => { let saved = { seen: [POPUP, GROK, "gone-notice"] }; const prefs = createDesktopPrefs({ read: () => saved, write: value => { saved = structuredClone(value); } }); assert.deepEqual(prefs.seen(), [POPUP, GROK]); prefs.markSeen(BOAT); assert.deepEqual(saved.seen, [POPUP, GROK, BOAT], "the new id is kept beside the ones an older page records"); assert.ok(prefs.seen().includes(BOAT) && prefs.seen().includes(POPUP), "the lookups an older page makes"); }); test("What's new, Boat's credit offer and the Grok Bot move are the notices a page can record; seen and analytics survive a restart; a broken record never stops the app", () => { assert.deepEqual([...DESKTOP_NOTICES], [POPUP, BOAT, GROK, MODELGO]); let saved = null; const writes = []; const prefs = createDesktopPrefs({ read: () => saved, write: value => { writes.push(value); saved = structuredClone(value); } }); assert.deepEqual(prefs.seen(), []); assert.equal(prefs.analytics(), null); for (const id of ["other-notice", "__proto__", "", 42]) prefs.markSeen(id); assert.equal(writes.length, 0, "nothing a page names outside the list is written"); prefs.markSeen(POPUP); prefs.markSeen(POPUP); assert.deepEqual(prefs.seen(), [POPUP]); assert.equal(writes.length, 1); prefs.markSeen(BOAT); assert.deepEqual(prefs.seen(), [POPUP, BOAT]); assert.equal(writes.length, 2); prefs.setAnalytics(false); const restarted = createDesktopPrefs({ read: () => saved, write: value => { saved = value; } }); assert.deepEqual(restarted.seen(), [POPUP, BOAT]); assert.equal(restarted.analytics(), false); restarted.setAnalytics("yes"); assert.equal(restarted.analytics(), false, "only true turns it on"); restarted.setAnalytics(true); assert.equal(restarted.analytics(), true); const broken = createDesktopPrefs({ read: () => { throw new Error("unreadable"); }, write: () => { throw new Error("read-only disk"); } }); assert.deepEqual(broken.seen(), []); broken.markSeen(POPUP); broken.setAnalytics(false); assert.deepEqual(broken.seen(), [POPUP], "kept for this launch when the file cannot be written"); assert.equal(broken.analytics(), false); for (const record of ["nonsense", [POPUP], { seen: "all", shown: [], analytics: "off" }, { seen: [POPUP, ""], shown: { [POPUP]: "today" } }]) { const prefs = createDesktopPrefs({ read: () => record, write: () => {} }); assert.equal(prefs.analytics(), null, JSON.stringify(record)); assert.ok(prefs.seen().every(id => id === POPUP), JSON.stringify(record)); } }); test("a write that fails leaves the last whole record on disk, analytics opt-out included", () => { const dir = mkdtempSync(path.join(tmpdir(), "desktop-prefs-")); try { const file = path.join(dir, "desktop-prefs.json"); createDesktopPrefs(jsonFile(file)).setAnalytics(false); assert.deepEqual(JSON.parse(readFileSync(file, "utf8")), { seen: [], analytics: false }); // As a full disk or a file-size limit does: the file is opened and emptied, then the write fails. const full = { ...fs, writeFileSync: target => { fs.writeFileSync(target, ""); throw Object.assign(new Error("file too large"), { code: "EFBIG" }); } }; const launch = createDesktopPrefs({ ...jsonFile(file, full) }); launch.markSeen(POPUP); assert.deepEqual(launch.seen(), [POPUP], "kept for this launch"); assert.throws(() => jsonFile(file, full).write({ seen: [] }), /file too large/); assert.deepEqual(readdirSync(dir), ["desktop-prefs.json"], "no temporary file is left behind"); const next = createDesktopPrefs(jsonFile(file)); assert.equal(next.analytics(), false, "the opt-out survives"); assert.deepEqual(next.seen(), []); assert.deepEqual(createDesktopPrefs(jsonFile(path.join(dir, "missing.json"))).seen(), [], "no file yet"); const notepad = path.join(dir, "config.json"); fs.writeFileSync(notepad, `\uFEFF${JSON.stringify({ onboarding: { hintsSeen: [ONBOARDING_CLOUD_HINT] } })}`); assert.deepEqual(onboardingHints(jsonFile(notepad).read()), [ONBOARDING_CLOUD_HINT], "a byte-order mark, as Notepad saves on Windows"); } finally { rmSync(dir, { recursive: true, force: true }); } }); const WELCOMED = { onboarding: { completedAt: "2026-10-01T09:00:00.000Z", version: WELCOME_VERSION, hintsSeen: [] } }; function desktopMain({ offered = true, page = event => event.page, config = () => WELCOMED, record = null } = {}) { const handlers = new Map(), listeners = new Map(), calls = [], told = [], languages = []; let saved = record, now = NOON; registerDesktopPrefs({ ipcMain: { handle: (channel, handler) => handlers.set(channel, handler), on: (channel, listener) => listeners.set(channel, listener) }, read: () => saved, write: value => { saved = structuredClone(value); }, page, cloudOffered: async () => { calls.push("offered"); return offered; }, localConfig: () => config(), languageChanged: (language, sender) => told.push([language, sender]), onLanguage: language => languages.push(language), now: () => now, systemName: async () => "Ada Lovelace", }); const ask = (channel, page, ...args) => handlers.get(channel)({ page, sender: `${page}-page` }, ...args); const answer = channel => page => { const event = { page }; listeners.get(channel)(event); return event.returnValue; }; return { ask, read: answer("desktop-analytics:read"), language: answer("desktop-language:read"), told, languages, calls, channels: [...handlers.keys()], saved: () => saved, later: ms => { now += ms; } }; } test("every page of this desktop's window reads and records the same notices; nothing else is answered", async () => { const main = desktopMain(); assert.deepEqual(main.channels, ["desktop-notices:state", "desktop-notices:seen", "desktop-profile:system-name", "desktop-analytics:set", "desktop-language:set", "window-frame:state", "window-frame:set"]); for (const page of ["local", "cloud", "server"]) assert.deepEqual(await main.ask("desktop-notices:state", page), { seen: [], cloud: true }); await main.ask("desktop-notices:seen", "server", POPUP); assert.deepEqual(await main.ask("desktop-notices:state", "local"), { seen: [POPUP], cloud: true }); for (const channel of main.channels) assert.throws(() => main.ask(channel, null, POPUP), /only available in this app's window/, channel); assert.deepEqual((await desktopMain({ offered: false }).ask("desktop-notices:state", "server")).cloud, false); }); test("a first run's default name for the profile goes to this computer's own page only", async () => { const main = desktopMain(); assert.equal(await main.ask("desktop-profile:system-name", "local"), "Ada Lovelace"); for (const page of ["cloud", "server"]) assert.throws(() => main.ask("desktop-profile:system-name", page), /only available in this app's window/, page); }); test("This computer's own record counts on every page before its page loads on this build: an earlier close; the welcome's Cloud beat does not", async () => { assert.equal(ONBOARDING_CLOUD_HINT, "onboarding-cloud-v1"); const record = (onboarding) => () => ({ composio: { apiKey: "" }, onboarding }); for (const [hint, seen] of [[POPUP, [POPUP]], [ONBOARDING_CLOUD_HINT, []]]) { const main = desktopMain({ config: record({ completedAt: "2026-10-01", version: 1, hintsSeen: ["tour-finished", hint] }) }); for (const page of ["local", "cloud", "server"]) assert.deepEqual(await main.ask("desktop-notices:state", page), { seen, cloud: true }, `${hint} ${page}`); assert.equal(main.saved(), null, "read, never written into this desktop's record"); } assert.deepEqual(onboardingHints({ onboarding: { completedAt: "x", version: 2, hintsSeen: ["a", 1, null, "b"] } }), ["a", "b"]); }); test("every desktop has What's new still to show, whatever This computer's welcome record says or lacks", async () => { // An install from before the welcome kept a record, a welcome never finished, no config.json, or one main cannot read. const records = [() => ({ onboarding: { completedAt: "", version: 0, hintsSeen: [] } }), () => ({ composio: {} }), () => null, () => "nonsense", () => { throw new Error("ENOENT"); }, () => { throw new SyntaxError("Unexpected token"); }, () => ({ onboarding: { hintsSeen: POPUP } }), () => ({ onboarding: { hintsSeen: [{ id: POPUP }] } })]; for (const config of records) { for (const page of ["local", "cloud", "server"]) { const main = desktopMain({ config }), name = `${String(config)} ${page}`; assert.deepEqual(await main.ask("desktop-notices:state", page), { seen: [], cloud: true }, name); } } }); test("the analytics switch is This computer's page's to set; a server's page in the window is told only that it is off", async () => { const main = desktopMain(); for (const page of ["local", "cloud", "server", null]) assert.equal(main.read(page), null, String(page)); await main.ask("desktop-analytics:set", "local", false); assert.equal(main.read("local"), false); assert.equal(main.read("cloud"), false, "My Cloud's page starts with analytics off"); assert.equal(main.read("server"), false); assert.equal(main.read(null), null, "a page outside this desktop's window is told nothing"); for (const page of ["cloud", "server"]) assert.throws(() => main.ask("desktop-analytics:set", page, true), /only available/); assert.equal(main.read("local"), false); await main.ask("desktop-analytics:set", "local", true); assert.equal(main.read("local"), true); assert.equal(main.read("cloud"), null, "a server's page never learns it is on"); assert.equal(main.read("server"), null); const broken = desktopMain({ page: () => { throw new Error("Object has been destroyed"); } }); assert.equal(broken.read("local"), null); }); test("the app language picked on any page of the window is the one every page reads, and it outlives a restart", async () => { const main = desktopMain(); for (const page of ["local", "cloud", "server", null]) assert.equal(main.language(page), null, `System until a pick: ${page}`); assert.equal(await main.ask("desktop-language:set", "local", "en"), true); for (const page of ["local", "cloud", "server"]) assert.equal(main.language(page), "en", page); assert.deepEqual(main.told, [["en", "local-page"]], "the other pages hear it"); assert.equal(await main.ask("desktop-language:set", "server", "de"), true, "a team's or a saved server's page picks for this computer too"); assert.equal(await main.ask("desktop-language:set", "cloud", "de"), true); assert.equal(main.language("local"), "de"); assert.deepEqual(main.told, [["en", "local-page"], ["de", "server-page"]], "told once per change"); assert.equal(await main.ask("desktop-language:set", "cloud", ""), true, "System is a pick too"); assert.equal(main.language("server"), ""); assert.equal(main.language(null), null, "a page outside this desktop's window is told nothing"); for (const bad of ["../../etc", "", "x".repeat(40), 42, null, undefined, { toString: () => "en" }]) { assert.equal(await main.ask("desktop-language:set", "local", bad), false, String(bad)); } assert.throws(() => main.ask("desktop-language:set", null, "en"), /only available in this app's window/); assert.equal(main.language("local"), ""); assert.equal(main.told.length, 3); const restarted = desktopMain({ record: main.saved() }); assert.equal(restarted.language("cloud"), ""); await restarted.ask("desktop-language:set", "local", "pt-br"); assert.equal(desktopMain({ record: restarted.saved() }).language("server"), "pt-br"); for (const record of [{ language: "