lobehub/.agents/skills/desktop/references/window-management.md
Innei fcdaf9d814 🔧 chore: update eslint v2 configuration and suppressions (#12133)
* v2 init

* chore: update eslint suppressions and package dependencies

- Removed several eslint suppressions related to array sorting and reversing from eslint-suppressions.json to clean up the configuration.
- Updated @lobehub/lint package version from 2.0.0-beta.6 to 2.0.0-beta.7 in package.json for improvements and bug fixes.
- Made minor formatting adjustments in vitest.config.mts and various SKILL.md files for better readability and consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* fix: clean up import statements and formatting

- Removed unnecessary whitespace in replaceComponentImports.ts for improved readability.
- Standardized import statements in contextEngineering.ts and createAgentExecutors.ts by adding missing spaces for consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update eslint suppressions and clean up code formatting

* 🐛 fix: use vi.hoisted for mock variable initialization

Fix TDZ error in persona service test by using vi.hoisted() to ensure
mock variables are available when vi.mock factory runs.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-02-11 13:04:48 +08:00

3.3 KiB

Desktop Window Management Guide

Window Management Overview

  1. Window creation and configuration
  2. Window state management (size, position, maximize)
  3. Multi-window coordination
  4. Window event handling

File Structure

apps/desktop/src/main/
├── appBrowsers.ts              # Core window management
├── controllers/
│   └── BrowserWindowsCtr.ts    # Window controller
└── modules/
    └── browserWindowManager.ts # Window manager module

Window Creation

export const createMainWindow = () => {
  const mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 600,
    minHeight: 400,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  if (isDev) {
    mainWindow.loadURL('http://localhost:3000');
  } else {
    mainWindow.loadFile(path.join(__dirname, '../../renderer/index.html'));
  }

  return mainWindow;
};

Window State Persistence

const saveWindowState = (window: BrowserWindow) => {
  if (!window.isMinimized() && !window.isMaximized()) {
    const [x, y] = window.getPosition();
    const [width, height] = window.getSize();
    settings.set('windowState', { x, y, width, height });
  }
};

const restoreWindowState = (window: BrowserWindow) => {
  const state = settings.get('windowState');
  if (state) {
    window.setBounds({ x: state.x, y: state.y, width: state.width, height: state.height });
  }
};

window.on('close', () => saveWindowState(window));

Multi-Window Management

export class WindowManager {
  private windows: Map<string, BrowserWindow> = new Map();

  createWindow(id: string, options: BrowserWindowConstructorOptions) {
    const window = new BrowserWindow(options);
    this.windows.set(id, window);
    window.on('closed', () => this.windows.delete(id));
    return window;
  }

  getWindow(id: string) {
    return this.windows.get(id);
  }
}

Window IPC Controller

// apps/desktop/src/main/controllers/BrowserWindowsCtr.ts
export default class BrowserWindowsCtr extends ControllerModule {
  static override readonly groupName = 'windows';

  @IpcMethod()
  minimizeWindow() {
    BrowserWindow.getFocusedWindow()?.minimize();
    return { success: true };
  }

  @IpcMethod()
  maximizeWindow() {
    const win = BrowserWindow.getFocusedWindow();
    win?.isMaximized() ? win.restore() : win?.maximize();
    return { success: true };
  }
}

Renderer Service

// src/services/electron/windowService.ts
import { ensureElectronIpc } from '@/utils/electron/ipc';

const ipc = ensureElectronIpc();

export const windowService = {
  minimize: () => ipc.windows.minimizeWindow(),
  maximize: () => ipc.windows.maximizeWindow(),
  close: () => ipc.windows.closeWindow(),
};

Frameless Window

const window = new BrowserWindow({
  frame: false,
  titleBarStyle: 'hidden',
});
.titlebar {
  -webkit-app-region: drag;
}
.titlebar-button {
  -webkit-app-region: no-drag;
}

Best Practices

  1. Use show: false initially, show after content loads
  2. Always set secure webPreferences
  3. Handle webContents.on('crashed') for recovery
  4. Clean up resources on window.on('closed')