Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/UAnirudh/IntelliPlan/llms.txt

Use this file to discover all available pages before exploring further.

The IntelliPlan desktop app is a real native application — not a PWA shortcut and not a browser bookmark — built with Electron and packaged with electron-builder. It lives in the desktop/ directory and delivers capabilities that a browser tab simply cannot provide: OS notifications that fire whether or not any window is open, a system tray entry showing your next scheduled session, a global keyboard shortcut reachable from any application, and persistent sign-in that survives quitting and restarting. It targets Windows, macOS, and Linux from one codebase and produces platform-native installers for each.

Why a Native App

The web app is rendered inside the window — you get the same feature set — but the following only exist in the desktop build:
CapabilityWhy it needs a native app
OS notificationsFire whether or not a window is open. The browser Notification API only works while the page is alive, so a closed tab means a missed session reminder.
Tray “up next”The next scheduled session is visible without opening anything. Refreshes every minute and on wake from sleep.
Global shortcutCtrl/Cmd+Shift+S starts a study session from any application, no Alt-Tab required.
Persistent sessionSign in once. The session survives quitting and restarting the app.
Offline handlingA dropped connection shows a real reconnecting message instead of Chromium’s error page.
Deep linksintelliplan://active opens straight to the session screen from any other app.

Running Locally

1

Install dependencies

cd desktop
npm install
2

Start against production

npm start
This runs electron . with IP_TARGET unset, so the window loads https://intelliplan.tech.
3

Start against a local server

npm run dev
# equivalent to: IP_TARGET=http://127.0.0.1:3000 electron .
Or set IP_TARGET explicitly to any URL:
IP_TARGET=http://127.0.0.1:3000 npm start

Building Installers

Each platform must be built on its own runner — electron-builder cannot produce a signed macOS build from Windows, and .github/workflows/desktop-release.yml enforces this in CI.
npm run dist:win
Produces an NSIS installer (x64) and an arm64 zip in dist/. Artifact names follow the pattern IntelliPlan-Setup-{version}-{arch}.exe.
The NSIS installer is x64 only. electron-builder 26.15.3 produces an ARM64 NSIS package that silently omits IntelliPlan.exe and all DLLs — the installer exits 0 and reports success while writing nothing runnable. The arm64 zip contains the native binary for Snapdragon / Copilot+ PCs but installs no Start Menu shortcuts and does not support auto-update. The CI smoke-test now fails the job if IntelliPlan.exe is absent, so this regression cannot ship again.

Publishing a Release

Tagging triggers the CI workflow, which builds all three platforms and attaches the installers to a GitHub Release. The IntelliPlan website’s /download page reads the latest release from the GitHub API and lists whatever assets it finds — no filenames to keep in sync, and a platform that failed to build is simply absent rather than a dead link.
# 1. Bump the version in desktop/package.json
npm version patch --no-git-tag-version

# 2. Commit and tag
git commit -am "chore(desktop): 1.0.1"
git tag -a desktop-v1.0.1 -m "IntelliPlan desktop 1.0.1"
git push origin main desktop-v1.0.1
The tag must start with desktop-v. The release script desktop_releases.py only considers tags matching that prefix, so a web or extension tag is never mistaken for a desktop build.

electron-builder Configuration

Four fields in desktop/package.json are load-bearing for packaging:
desktop/package.json (build section, abridged)
{
  "name": "intelliplan-desktop",
  "productName": "IntelliPlan",
  "version": "1.0.0",
  "build": {
    "appId": "tech.intelliplan.desktop",
    "publish": [{ "provider": "github", "owner": "UAnirudh", "repo": "IntelliPlan" }],
    "protocols": [{ "name": "IntelliPlan", "schemes": ["intelliplan"] }],
    "win": {
      "target": [
        { "target": "nsis", "arch": ["x64"] },
        { "target": "zip",  "arch": ["arm64"] }
      ]
    },
    "mac": {
      "category": "public.app-category.education",
      "target": [
        { "target": "dmg", "arch": ["x64", "arm64"] },
        { "target": "zip", "arch": ["x64", "arm64"] }
      ],
      "hardenedRuntime": true
    },
    "linux": {
      "category": "Education",
      "target": [
        { "target": "AppImage", "arch": ["x64", "arm64"] },
        { "target": "deb",      "arch": ["x64", "arm64"] },
        { "target": "rpm",      "arch": ["x64"] }
      ]
    }
  }
}
FieldWhy
homepagedeb and rpm builds refuse to run without it
author.emailBecomes the deb/rpm maintainer field; also required
repositoryelectron-builder warns and cannot infer the publish target
build.publishWithout a provider, update-info generation dereferences null

Code Signing

Builds are unsigned by default — no certificate is in the repo. CI sets CSC_IDENTITY_AUTO_DISCOVERY=false to stop electron-builder scanning the runner’s keychain. Signing turns on by adding repository secrets; nothing in the code changes.
The app is not yet code-signed. Windows SmartScreen and macOS Gatekeeper will warn users on first launch. The CI plumbing is complete — the only missing piece is the certificates themselves.
Azure Trusted Signing (~$10/month) is the recommended route. An OV .pfx certificate is signed but still triggers SmartScreen until reputation builds — buying little over unsigned. An EV certificate requires a hardware token a CI runner cannot hold.
SecretValue
AZURE_CODE_SIGNING_ACCOUNTTrusted Signing account name
AZURE_CODE_SIGNING_PROFILECertificate profile name
AZURE_CODE_SIGNING_ENDPOINTRegion endpoint (e.g. https://eus.codesigning.azure.net)
AZURE_TENANT_IDApp registration tenant
AZURE_CLIENT_IDApp registration client ID
AZURE_CLIENT_SECRETApp registration secret
Every release carries a SHA256SUMS.txt covering the platforms that built. It lets a student verify a download arrived intact and is the only integrity signal an unsigned build has.

Google Sign-In

Google refuses to run OAuth inside an embedded browser view — hardest of all against Family Link supervised accounts, which is a large share of the student population this app serves. The desktop app never shows Google’s sign-in page in the Electron window. Instead it uses a PKCE-based redirect flow through the system browser:
1

App generates a PKCE challenge

startGoogleSignIn() invents a verifier, keeps it in memory, and opens https://intelliplan.tech/login/google?desktop=<challenge> in the system browser.
2

Browser completes Google's OAuth flow

The user signs in as normal through their real browser. The callback server sees the stored challenge, mints a one-time code, and redirects to intelliplan://auth?code=….
3

App redeems the code

The deep link wakes the app. It calls POST /api/desktop/auth/exchange with the code and the verifier. The request runs as a fetch inside the window so the session cookie lands in the jar the app actually browses with.
The one-time code travels in a deep-link URL that any local program could intercept. It is only half a credential — redeeming it also requires the PKCE verifier, which never leaves the Electron process. Codes are single-use, expire in two minutes, and are stored only as SHA-256. Rules live in desktop_auth.py, tested in tests/test_desktop_auth.py.

Security Model

The renderer process is fully sandboxed: contextIsolation: true, nodeIntegration: false, sandbox: true. Page scripts reach the main process only through the three functions exposed in src/preload.js.
No generic IPC channel. A bridge that forwards arbitrary channel names from page script is the standard path to remote code execution in Electron apps. The notify() bridge accepts a title, a body, and a same-app path — a full URL is rejected outright, so page script cannot ask the main process to navigate the window to an external destination.Navigation is pinned to the app’s origin. Any link to a different origin opens in the system browser. This keeps stray links out of the application shell and is also required for Google sign-in, which refuses to run in an embedded view.Permission allowlist. Notifications, media (for the focus check-in), fullscreen, and sanitised clipboard writes are allowed. Everything else is denied without prompting.

Known Limitations and Roadmap

electron-updater is already listed as a dependency in desktop/package.json and fits the existing electron-builder config. What it still needs is a release feed to publish to.
The CI workflow reads signing credentials from repository secrets and signs automatically when they are set. Nothing in the code needs to change — the only missing piece is the certificate. See the Code Signing section above for the exact secrets required per platform.
electron-builder 26.15.3 produces an ARM64 NSIS installer that reports success but writes no executable. The ARM64 build ships as a .zip instead. The x64 installer works correctly on ARM64 hardware (Snapdragon X1E80100 verified) under Windows’ x64 translation layer, at some cost in performance. Restore arm64 to build.win.target[0].arch when upstream fixes the issue.
Icons are currently the 512 px PWA icon. Platform-native .icns (macOS) and .ico (Windows) sets would render more crisply at small sizes, particularly in the taskbar and Start Menu.

Platform Build Matrix

PlatformInstaller FormatArchitecturesNotes
WindowsNSIS installer (.exe)x64Verified working on ARM64 hardware via translation
WindowsZip (.zip)arm64No shortcuts; no auto-update; native binary
macOSDMG (.dmg) + Zipx64, arm64Hardened runtime; notarisation ready
LinuxAppImage (.AppImage)x64, arm64Self-contained, no install required
LinuxDebian package (.deb)x64, arm64Installs via dpkg -i
LinuxRPM package (.rpm)x64For Fedora / RHEL-based distributions

Build docs developers (and LLMs) love