Cortask

Desktop Features

Custom window management

The app uses a frameless Electron BrowserWindow with titleBarStyle: "hidden". The default application menu is removed entirely. Window controls (minimize, maximize/restore, close) are implemented as IPC calls from the renderer:

  • window:minimize -- minimize the window.
  • window:maximize -- toggle between maximized and restored state.
  • window:close -- close the window (which hides to tray when the tray is active).

The main process notifies the renderer of maximize state changes via window:maximizeChanged events so the UI can update its title bar icon accordingly. The renderer can also query the current state with window:isMaximized.

Default window dimensions are 1200x800 with a minimum of 800x600.

System tray integration

A system tray icon is created on startup with a context menu containing two entries:

  • Show Cortask -- bring the main window to the foreground.
  • Quit -- stop the backend, destroy the tray, and exit the process.

Double-clicking the tray icon also restores the window.

When the tray is active, closing the window hides it instead of quitting the app. The backend continues running in the background so scheduled cron jobs, channel integrations, and API endpoints remain available. To fully exit, use the Quit option in the tray menu.

On macOS, the activate event (clicking the dock icon) also restores the window.

File dialogs

The desktop app exposes a browse-folder IPC handler that opens the native OS folder picker dialog. The web UI calls this when selecting a workspace directory, providing a better experience than typing a path manually. The dialog title reads "Select Workspace Folder" and returns the selected path or null if cancelled.

Shell integration

Two shell actions are available from the renderer via IPC:

  • Open path (shell:open-path) -- opens a file or folder with the OS default application (e.g. opens a directory in Finder/Explorer, a .pdf in your PDF viewer).
  • Show in folder (shell:show-in-folder) -- opens the parent directory in the file manager and highlights the target file.

These are used throughout the UI wherever file or workspace paths are displayed.

Auto-updater

The app uses electron-updater with GitHub as the publish provider. Updates are checked automatically 5 seconds after launch and can also be triggered manually from the UI.

The update lifecycle exposes four IPC methods:

MethodDescription
updater:checkCheck for available updates. Returns update info or null.
updater:downloadDownload the update. Progress events stream to the renderer.
updater:installQuit the app and install the downloaded update.
updater:onStatusSubscribe to status events in the renderer.

Status events sent to the renderer include:

  • checking -- update check in progress.
  • available -- a new version was found (includes version number and release notes).
  • up-to-date -- already on the latest version.
  • downloading -- download in progress (includes percent, bytes per second, transferred, and total).
  • downloaded -- ready to install.
  • error -- something went wrong (includes error message).

Auto-download is disabled (autoDownload: false), so the user must explicitly trigger the download. However, autoInstallOnAppQuit is enabled, meaning a downloaded update installs automatically the next time the app exits.

Single instance enforcement

The app calls app.requestSingleInstanceLock() at startup. If another instance is already running, the new process quits immediately. The existing instance receives a second-instance event and responds by restoring and focusing its main window. This prevents duplicate backend servers from competing for the same port and data directory.

OS-level credential encryption

On platforms where Electron's safeStorage is available, the master encryption key for the credential store is encrypted using the OS keychain rather than stored as a plaintext file. On first run the app generates a random 32-byte key and encrypts it via safeStorage.encryptString. Existing plaintext keys are migrated automatically. If safeStorage is unavailable, the app falls back to a plaintext key file with restricted permissions (0o600).