chore(lint): clean up desktop and highlight packages

This commit is contained in:
Mohamed Boudra
2026-04-23 23:27:15 +07:00
parent e249649656
commit de1a7ef74b
8 changed files with 262 additions and 226 deletions

View File

@@ -144,25 +144,13 @@ async function inspectTitlebarRegions(page) {
};
}
const dragSummaries = [];
const suspiciousDragHosts = [];
for (const node of nodes) {
if (!(node instanceof HTMLElement) || !isVisible(node)) {
continue;
}
const summary = summarizeElement(node);
if (summary.appRegion !== "drag") {
continue;
}
function buildDragRecord(node, summary) {
const parent = node.parentElement instanceof HTMLElement ? node.parentElement : null;
const parentSummary = parent ? summarizeElement(parent) : null;
const interactiveDescendants = Array.from(node.querySelectorAll(interactiveSelector))
.filter((child) => child instanceof HTMLElement)
.filter((child) => isVisible(child))
.map((child) => summarizeInteractive(child));
const siblingResizers = parent
? Array.from(parent.children)
.filter((child) => child !== node)
@@ -170,7 +158,6 @@ async function inspectTitlebarRegions(page) {
.filter((child) => isTopResizer(child, summary))
.map((child) => summarizeElement(child))
: [];
const parentInteractive = parent
? Array.from(parent.querySelectorAll(interactiveSelector))
.filter((child) => child instanceof HTMLElement)
@@ -180,7 +167,6 @@ async function inspectTitlebarRegions(page) {
const explicitNoDragInteractive = parentInteractive.filter(
(child) => child.appRegion === "no-drag",
);
const record = {
...summary,
parent: parentSummary,
@@ -189,17 +175,25 @@ async function inspectTitlebarRegions(page) {
explicitNoDragInteractive: explicitNoDragInteractive.slice(0, 5),
parentInteractiveCount: parentInteractive.length,
};
dragSummaries.push(record);
const looksLikeHostShortcut =
isNearTop(summary) &&
(summary.position !== "absolute" ||
summary.text.length > 0 ||
interactiveDescendants.length > 0 ||
parentSummary?.appRegion === "drag");
if (looksLikeHostShortcut) {
suspiciousDragHosts.push(record);
}
return { record, looksLikeHostShortcut };
}
const dragSummaries = [];
const suspiciousDragHosts = [];
for (const node of nodes) {
if (!(node instanceof HTMLElement) || !isVisible(node)) continue;
const summary = summarizeElement(node);
if (summary.appRegion !== "drag") continue;
const { record, looksLikeHostShortcut } = buildDragRecord(node, summary);
dragSummaries.push(record);
if (looksLikeHostShortcut) suspiciousDragHosts.push(record);
}
const verifiedRegions = dragSummaries
@@ -230,24 +224,27 @@ async function inspectTitlebarRegions(page) {
Math.abs(summary.height - candidate.height) <= 1
);
});
function annotateMatchingParent(parent) {
if (!(parent instanceof HTMLElement)) return;
const resizers = Array.from(parent.children).filter(
(child) => child instanceof HTMLElement && isTopResizer(child, candidate),
);
for (const child of resizers) {
child.setAttribute("data-electron-verify-resizer", "true");
}
const interactiveChildren = Array.from(parent.querySelectorAll(interactiveSelector))
.filter((child) => child instanceof HTMLElement)
.filter((child) => isVisible(child))
.filter((child) => summarizeElement(child).appRegion === "no-drag")
.slice(0, 3);
for (const child of interactiveChildren) {
child.setAttribute("data-electron-verify-interactive", "true");
}
}
if (matchingDragNode instanceof HTMLElement) {
matchingDragNode.setAttribute("data-electron-verify-drag", "true");
const parent = matchingDragNode.parentElement;
if (parent instanceof HTMLElement) {
for (const child of parent.children) {
if (child instanceof HTMLElement && isTopResizer(child, candidate)) {
child.setAttribute("data-electron-verify-resizer", "true");
}
}
const interactiveChildren = Array.from(parent.querySelectorAll(interactiveSelector))
.filter((child) => child instanceof HTMLElement)
.filter((child) => isVisible(child))
.filter((child) => summarizeElement(child).appRegion === "no-drag")
.slice(0, 3);
for (const child of interactiveChildren) {
child.setAttribute("data-electron-verify-interactive", "true");
}
}
annotateMatchingParent(matchingDragNode.parentElement);
}
}
@@ -367,37 +364,26 @@ async function findAppPage(browser) {
throw new Error(`Unable to find Electron app page for ${APP_URL_FRAGMENT}`);
}
async function main() {
await ensureDir(OUTPUT_DIR);
const browser = await chromium.connectOverCDP(CDP_URL);
const page = await findAppPage(browser);
const consoleMessages = [];
const results = [];
function attachConsoleCollector(page, consoleMessages) {
page.on("console", (message) => {
consoleMessages.push({
type: message.type(),
text: message.text(),
});
consoleMessages.push({ type: message.type(), text: message.text() });
});
page.on("pageerror", (error) => {
consoleMessages.push({
type: "pageerror",
text: String(error),
});
consoleMessages.push({ type: "pageerror", text: String(error) });
});
}
async function navigateToWelcome(page) {
await page.waitForLoadState("domcontentloaded");
await page.waitForTimeout(1000);
if (!page.url().endsWith("/welcome")) {
await page.goto(`http://${APP_URL_FRAGMENT}/welcome`, { waitUntil: "domcontentloaded" });
await page.waitForTimeout(1000);
}
}
const welcomeScreenshot = await captureScreenshot(page, "01-welcome.png");
const desktopDetection = await page.evaluate(() => {
async function detectDesktopBridge(page) {
return page.evaluate(() => {
const bridge = window.paseoDesktop;
const keys = bridge && typeof bridge === "object" ? Object.keys(bridge) : [];
const keyTypes =
@@ -411,6 +397,124 @@ async function main() {
platform: bridge?.platform ?? null,
};
});
}
async function navigateToSettings(page, serverId) {
await page.evaluate((nextServerId) => {
window.location.href = `/h/${nextServerId}/settings`;
}, serverId);
await page.waitForURL(new RegExp(`/h/${escapeRegExp(serverId)}/settings$`), {
timeout: 30_000,
});
await page.getByText("Daemon management", { exact: true }).waitFor({
timeout: 30_000,
});
}
async function dismissMobileSidebarIfVisible(page) {
const sidebarSettingsButton = page.locator('[data-testid="sidebar-settings"]').first();
const menuToggle = page.locator('[data-testid="menu-button"]').first();
const bothVisible =
(await sidebarSettingsButton.isVisible().catch(() => false)) &&
(await menuToggle.isVisible().catch(() => false));
if (!bothVisible) return;
await menuToggle.click();
await sidebarSettingsButton.waitFor({ state: "hidden", timeout: 10_000 }).catch(() => undefined);
await page.waitForTimeout(500);
}
function evaluateDragRegionCheck(dragRegionCheck) {
return (
dragRegionCheck.dragRegionCount > 0 &&
dragRegionCheck.verifiedRegionCount > 0 &&
Boolean(dragRegionCheck.candidate) &&
dragRegionCheck.candidate.top < 220 &&
dragRegionCheck.candidate.parent?.appRegion !== "drag" &&
dragRegionCheck.candidate.siblingResizers.length > 0 &&
dragRegionCheck.suspiciousDragHosts.length === 0
);
}
function evaluateTrafficLightPadding(dragRegionCheck) {
if (process.platform !== "darwin") return true;
const observedPaddingLeft = dragRegionCheck.candidate?.parent?.paddingLeft ?? null;
return (
typeof observedPaddingLeft === "number" &&
observedPaddingLeft >= 78 &&
observedPaddingLeft <= 110
);
}
async function collectDragRegionResults(page, dragRegionCheck, dragScreenshot, results) {
results.push({
check: "titlebar-drag-structure",
pass: evaluateDragRegionCheck(dragRegionCheck),
details: dragRegionCheck,
screenshot: dragScreenshot,
});
const trafficLightScreenshot = await captureScreenshot(page, "04-traffic-light-padding.png");
results.push({
check: "traffic-light-padding",
pass: evaluateTrafficLightPadding(dragRegionCheck),
details: {
platform: process.platform,
observedPaddingLeft: dragRegionCheck.candidate?.parent?.paddingLeft ?? null,
note: "Traffic-light padding is only validated structurally on macOS in this verifier.",
candidate: dragRegionCheck.candidate,
},
screenshot: trafficLightScreenshot,
});
results.push({
check: "interactive-no-drag-layering",
pass:
Boolean(dragRegionCheck.candidate) &&
Array.isArray(dragRegionCheck.candidate.explicitNoDragInteractive) &&
dragRegionCheck.candidate.explicitNoDragInteractive.length > 0,
details: {
candidate: dragRegionCheck.candidate,
explicitNoDragInteractive: dragRegionCheck.candidate?.explicitNoDragInteractive ?? [],
},
screenshot: dragScreenshot,
});
}
async function collectDaemonManagementResult(page, serverId, desktopStatus, results) {
const daemonManagementVisible = await Promise.all([
page.getByText("Built-in daemon", { exact: true }).isVisible(),
page.getByText("Daemon management", { exact: true }).isVisible(),
page.getByRole("button", { name: "Restart daemon" }).first().isVisible(),
]).then((values) => values.every(Boolean));
const daemonManagementScreenshot = await captureScreenshot(
page,
"05-settings-daemon-management.png",
);
results.push({
check: "settings-daemon-management",
pass: daemonManagementVisible,
details: {
route: page.url(),
serverId,
desktopStatus,
},
screenshot: daemonManagementScreenshot,
});
}
async function main() {
await ensureDir(OUTPUT_DIR);
const browser = await chromium.connectOverCDP(CDP_URL);
const page = await findAppPage(browser);
const consoleMessages = [];
const results = [];
attachConsoleCollector(page, consoleMessages);
await navigateToWelcome(page);
const welcomeScreenshot = await captureScreenshot(page, "01-welcome.png");
const desktopDetection = await detectDesktopBridge(page);
const hasExpectedDesktopShape =
desktopDetection.exists &&
@@ -432,84 +536,14 @@ async function main() {
);
const serverId = desktopStatus.serverId.trim();
await page.evaluate((nextServerId) => {
window.location.href = `/h/${nextServerId}/settings`;
}, serverId);
await page.waitForURL(new RegExp(`/h/${escapeRegExp(serverId)}/settings$`), {
timeout: 30_000,
});
await page.getByText("Daemon management", { exact: true }).waitFor({
timeout: 30_000,
});
await navigateToSettings(page, serverId);
const settingsScreenshot = await captureScreenshot(page, "02-settings-page.png");
const sidebarSettingsButton = page.locator('[data-testid="sidebar-settings"]').first();
const menuToggle = page.locator('[data-testid="menu-button"]').first();
if (
(await sidebarSettingsButton.isVisible().catch(() => false)) &&
(await menuToggle.isVisible().catch(() => false))
) {
await menuToggle.click();
await sidebarSettingsButton
.waitFor({ state: "hidden", timeout: 10_000 })
.catch(() => undefined);
await page.waitForTimeout(500);
}
await captureScreenshot(page, "02-settings-page.png");
await dismissMobileSidebarIfVisible(page);
const dragRegionCheck = await inspectTitlebarRegions(page);
const dragScreenshot = await captureScreenshot(page, "03-drag-region.png");
const dragRegionPassed =
dragRegionCheck.dragRegionCount > 0 &&
dragRegionCheck.verifiedRegionCount > 0 &&
Boolean(dragRegionCheck.candidate) &&
dragRegionCheck.candidate.top < 220 &&
dragRegionCheck.candidate.parent?.appRegion !== "drag" &&
dragRegionCheck.candidate.siblingResizers.length > 0 &&
dragRegionCheck.suspiciousDragHosts.length === 0;
results.push({
check: "titlebar-drag-structure",
pass: dragRegionPassed,
details: dragRegionCheck,
screenshot: dragScreenshot,
});
const trafficLightScreenshot = await captureScreenshot(page, "04-traffic-light-padding.png");
const isMac = process.platform === "darwin";
const observedPaddingLeft = dragRegionCheck.candidate?.parent?.paddingLeft ?? null;
const trafficLightPaddingPassed = !isMac
? true
: typeof observedPaddingLeft === "number" &&
observedPaddingLeft >= 78 &&
observedPaddingLeft <= 110;
results.push({
check: "traffic-light-padding",
pass: trafficLightPaddingPassed,
details: {
platform: process.platform,
observedPaddingLeft,
note: "Traffic-light padding is only validated structurally on macOS in this verifier.",
candidate: dragRegionCheck.candidate,
},
screenshot: trafficLightScreenshot,
});
const noDragInteractiveCheck = {
check: "interactive-no-drag-layering",
pass:
Boolean(dragRegionCheck.candidate) &&
Array.isArray(dragRegionCheck.candidate.explicitNoDragInteractive) &&
dragRegionCheck.candidate.explicitNoDragInteractive.length > 0,
details: {
candidate: dragRegionCheck.candidate,
explicitNoDragInteractive: dragRegionCheck.candidate?.explicitNoDragInteractive ?? [],
},
screenshot: dragScreenshot,
};
results.push(noDragInteractiveCheck);
await collectDragRegionResults(page, dragRegionCheck, dragScreenshot, results);
const fullscreenDetails = await inspectFullscreenResizer(page);
const fullscreenScreenshot = await captureScreenshot(page, "04-fullscreen-resizer.png");
@@ -520,26 +554,7 @@ async function main() {
screenshot: fullscreenScreenshot,
});
const daemonManagementVisible = await Promise.all([
page.getByText("Built-in daemon", { exact: true }).isVisible(),
page.getByText("Daemon management", { exact: true }).isVisible(),
page.getByRole("button", { name: "Restart daemon" }).first().isVisible(),
]).then((values) => values.every(Boolean));
const daemonManagementScreenshot = await captureScreenshot(
page,
"05-settings-daemon-management.png",
);
results.push({
check: "settings-daemon-management",
pass: daemonManagementVisible,
details: {
route: page.url(),
serverId,
desktopStatus,
},
screenshot: daemonManagementScreenshot,
});
await collectDaemonManagementResult(page, serverId, desktopStatus, results);
const desktopDetectionScreenshot = await captureScreenshot(page, "06-desktop-detection.png");
results[0].screenshot = desktopDetectionScreenshot;

View File

@@ -238,6 +238,47 @@ function normalizeVersion(version: string | null): string | null {
return trimmed.replace(/^v/i, "");
}
function shouldRestartForVersion(current: DesktopDaemonStatus): boolean {
if (!current.desktopManaged) return false;
const appVersion = normalizeVersion(resolveDesktopAppVersion());
const daemonVersion = normalizeVersion(current.version);
return Boolean(appVersion && daemonVersion && appVersion !== daemonVersion);
}
function buildStartupFailureError(
result: { code: number | null; signal: string | null; error?: Error },
stdout: string,
stderr: string,
): Error {
const reason = result.error
? result.error.message
: `exit code ${result.code ?? "unknown"}${result.signal ? ` (${result.signal})` : ""}`;
const parts = [`Daemon failed to start: ${reason}`];
if (stderr.trim()) parts.push(`stderr:\n${stderr.trim()}`);
if (stdout.trim()) parts.push(`stdout:\n${stdout.trim()}`);
const logs = tailFile(logFilePath(), 15);
if (logs) parts.push(`Recent logs (${logFilePath()}):\n${logs}`);
return new Error(parts.join("\n\n"));
}
async function pollForRunningDaemon(): Promise<DesktopDaemonStatus> {
for (let attempt = 0; attempt < STARTUP_POLL_MAX_ATTEMPTS; attempt++) {
const status = await resolveStatus();
if (attempt === 0 || attempt === STARTUP_POLL_MAX_ATTEMPTS - 1 || attempt % 10 === 9) {
logDesktopDaemonLifecycle("polling daemon status after detached start", {
attempt: attempt + 1,
status: status.status,
pid: status.pid,
listen: status.listen,
serverId: status.serverId || null,
});
}
if (status.status === "running" && status.serverId && status.listen) return status;
await sleep(STARTUP_POLL_INTERVAL_MS);
}
return resolveStatus();
}
async function startDaemon(): Promise<DesktopDaemonStatus> {
const current = await resolveStatus();
logDesktopDaemonLifecycle("initial status check before start", {
@@ -249,12 +290,10 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
desktopManaged: current.desktopManaged,
});
if (current.status === "running") {
const appVersion = normalizeVersion(resolveDesktopAppVersion());
const daemonVersion = normalizeVersion(current.version);
if (current.desktopManaged && appVersion && daemonVersion && appVersion !== daemonVersion) {
if (shouldRestartForVersion(current)) {
logDesktopDaemonLifecycle("daemon version mismatch, restarting", {
appVersion,
daemonVersion,
appVersion: normalizeVersion(resolveDesktopAppVersion()),
daemonVersion: normalizeVersion(current.version),
});
await stopDaemon();
} else {
@@ -340,34 +379,10 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
});
if (result.exitedEarly) {
const reason = result.error
? result.error.message
: `exit code ${result.code ?? "unknown"}${result.signal ? ` (${result.signal})` : ""}`;
const parts = [`Daemon failed to start: ${reason}`];
if (stderr.trim()) parts.push(`stderr:\n${stderr.trim()}`);
if (stdout.trim()) parts.push(`stdout:\n${stdout.trim()}`);
const logs = tailFile(logFilePath(), 15);
if (logs) parts.push(`Recent logs (${logFilePath()}):\n${logs}`);
throw new Error(parts.join("\n\n"));
throw buildStartupFailureError(result, stdout, stderr);
}
// Poll for PID file with server ID
for (let attempt = 0; attempt < STARTUP_POLL_MAX_ATTEMPTS; attempt++) {
const status = await resolveStatus();
if (attempt === 0 || attempt === STARTUP_POLL_MAX_ATTEMPTS - 1 || attempt % 10 === 9) {
logDesktopDaemonLifecycle("polling daemon status after detached start", {
attempt: attempt + 1,
status: status.status,
pid: status.pid,
listen: status.listen,
serverId: status.serverId || null,
});
}
if (status.status === "running" && status.serverId && status.listen) return status;
await sleep(STARTUP_POLL_INTERVAL_MS);
}
return await resolveStatus();
return pollForRunningDaemon();
}
async function stopDaemon(): Promise<DesktopDaemonStatus> {

View File

@@ -142,19 +142,11 @@ export async function garbageCollectManagedAttachmentFiles(input: {
: new Set<string>();
const entries = await readdir(dirPath, { withFileTypes: true });
let deletedCount = 0;
const toDelete = entries.filter(
(entry) => entry.isFile() && !referencedIds.has(path.parse(entry.name).name),
);
for (const entry of entries) {
if (!entry.isFile()) {
continue;
}
const attachmentId = path.parse(entry.name).name;
if (referencedIds.has(attachmentId)) {
continue;
}
await rm(path.join(dirPath, entry.name), { force: true });
deletedCount += 1;
}
await Promise.all(toDelete.map((entry) => rm(path.join(dirPath, entry.name), { force: true })));
return deletedCount;
return toDelete.length;
}

View File

@@ -15,12 +15,17 @@ interface OpenOptions {
filters?: Array<{ name: string; extensions: string[] }>;
}
function resolveDialogType(kind: AskOptions["kind"]): "warning" | "error" | "question" {
if (kind === "warning") return "warning";
if (kind === "error") return "error";
return "question";
}
export function registerDialogHandlers(): void {
ipcMain.handle("paseo:dialog:ask", async (event, message: string, options?: AskOptions) => {
const win = BrowserWindow.fromWebContents(event.sender);
const result = await dialog.showMessageBox(win ?? BrowserWindow.getFocusedWindow()!, {
type:
options?.kind === "warning" ? "warning" : options?.kind === "error" ? "error" : "question",
type: resolveDialogType(options?.kind),
title: options?.title ?? "Confirm",
message,
buttons: [options?.cancelLabel ?? "Cancel", options?.okLabel ?? "OK"],

View File

@@ -105,7 +105,7 @@ export function setupApplicationMenu(): void {
return;
}
const menu = Menu.buildFromTemplate([
const contextMenu = Menu.buildFromTemplate([
{
label: "Copy",
role: "copy",
@@ -124,6 +124,6 @@ export function setupApplicationMenu(): void {
},
]);
menu.popup({ window: win });
contextMenu.popup({ window: win });
});
}

View File

@@ -280,25 +280,27 @@ export async function installSkills(): Promise<InstallStatus> {
log.info("[integrations] installSkills", { sourceDir, agentsDir, claudeDir, codexDir });
for (const skillName of SKILL_NAMES) {
const sourceFile = path.join(sourceDir, skillName, "SKILL.md");
await copySkillFile(sourceFile, agentsDir, skillName);
await symlinkSkillDir(skillName, agentsDir, claudeDir);
await copySkillFile(sourceFile, codexDir, skillName);
}
await Promise.all(
SKILL_NAMES.map(async (skillName) => {
const sourceFile = path.join(sourceDir, skillName, "SKILL.md");
await copySkillFile(sourceFile, agentsDir, skillName);
await symlinkSkillDir(skillName, agentsDir, claudeDir);
await copySkillFile(sourceFile, codexDir, skillName);
}),
);
return getSkillsInstallStatus();
}
export async function getSkillsInstallStatus(): Promise<InstallStatus> {
const claudeDir = getClaudeSkillsDir();
for (const skillName of SKILL_NAMES) {
const skillFile = path.join(claudeDir, skillName, "SKILL.md");
try {
await fs.access(skillFile);
} catch {
return { installed: false };
}
}
return { installed: true };
const accessResults = await Promise.all(
SKILL_NAMES.map((skillName) =>
fs
.access(path.join(claudeDir, skillName, "SKILL.md"))
.then(() => true)
.catch(() => false),
),
);
return { installed: accessResults.every(Boolean) };
}

View File

@@ -120,20 +120,27 @@ function getAppDistDir(): string {
return path.resolve(__dirname, "../../app/dist");
}
function getWindowIconPath(): string | null {
const candidates = app.isPackaged
? process.platform === "win32"
? [path.join(process.resourcesPath, "icon.ico"), path.join(process.resourcesPath, "icon.png")]
: [path.join(process.resourcesPath, "icon.png")]
: process.platform === "darwin"
? [path.resolve(__dirname, "../assets/icon.png")]
: process.platform === "win32"
? [
path.resolve(__dirname, "../assets/icon.ico"),
path.resolve(__dirname, "../assets/icon.png"),
]
: [path.resolve(__dirname, "../assets/icon.png")];
function getWindowIconCandidates(): string[] {
if (app.isPackaged) {
if (process.platform === "win32") {
return [
path.join(process.resourcesPath, "icon.ico"),
path.join(process.resourcesPath, "icon.png"),
];
}
return [path.join(process.resourcesPath, "icon.png")];
}
if (process.platform === "win32") {
return [
path.resolve(__dirname, "../assets/icon.ico"),
path.resolve(__dirname, "../assets/icon.png"),
];
}
return [path.resolve(__dirname, "../assets/icon.png")];
}
function getWindowIconPath(): string | null {
const candidates = getWindowIconCandidates();
return candidates.find((candidate) => existsSync(candidate)) ?? null;
}

View File

@@ -62,7 +62,7 @@ export function highlightCode(code: string, filename: string): HighlightToken[][
}
// Build a map of character positions to styles
const styleMap: Array<HighlightStyle | null> = new Array(code.length).fill(null);
const styleMap: Array<HighlightStyle | null> = Array.from({ length: code.length }, () => null);
highlightTree(tree, highlighter, (from, to, classes) => {
for (let i = from; i < to && i < styleMap.length; i++) {