A small floating widget and a tray icon for tracking time in Factorial HR. Clock in, take a break, resume, clock out — without opening the browser.
The card sits on top of whatever you are working on and shows the day at a glance: how long you have worked, how much is left, and where the breaks were. Click it to open the full card, click again to collapse it.
Grab the latest build from the releases page.
| Platform | File | Notes |
|---|---|---|
| Windows | Factorial-Desktop-Setup-<version>.exe |
Installs for your user only — no admin rights needed. Adds a Start-menu entry and can start with Windows. This is the one to pick. |
| Windows | Factorial-Desktop.exe |
Runs from anywhere without installing. Handy for a USB stick or a locked-down machine. |
| macOS | Factorial-Desktop-<version>-arm64.dmg |
Apple Silicon. |
The first launch needs one extra step:
- Windows: the build is not signed, so SmartScreen shows a blue box. Click More info → Run anyway.
- macOS: nothing. The build is signed and notarized.
Signing in. The first launch opens Factorial's own login page in a window — the app never sees your password, and two-factor works exactly as it does in the browser. The session is kept, so the next launch goes straight to the widget.
The tray icon is where the app lives. It has no taskbar button and no dock icon on purpose: closing the widget only hides it. The icon's colour is the state at a glance — grey clocked out, green clocked in, amber on a break — and hovering shows the running time without opening anything.
Windows tip: new tray icons are hidden behind the
^chevron by default. Drag it onto the taskbar once and it stays there.
The tray menu is the way to everything: clock in and out, pick a break type, show or hide the widget, and Settings for start-with-system, always-on-top, which way the card opens, light or dark, the language, and checking for updates.
Updates. The installed build checks for a new version half a minute after launch and every six hours after that, and always asks before downloading anything. If a shift is running it will not restart — the update is applied the next time you quit. The portable build cannot replace itself, so it points at the download page instead.
One thing worth knowing: this writes to your real timesheet. The app never guesses a time — when it loses contact with Factorial it shows the last known state and says so, rather than inventing something that looks plausible.
Language. The app follows your system language and falls back to English for anything it does not speak. Seven are included: English, Deutsch, Español, Français, Italiano, Português and Nederlands. To pin one, use the tray menu → Settings → Language.
Node 22 or newer.
npm install
npm run dev
| Command | Purpose |
|---|---|
npm run dev |
Dev mode with hot reload (electron-vite) |
npm test |
Unit tests (Vitest, no Electron runtime needed) |
npm run test:watch |
The same tests, watching |
npm run typecheck |
TypeScript across main, preload, shared and renderer |
npm run build |
Typecheck, then build to out/ |
npm run package:mac |
macOS: DMG + ZIP, arm64 |
npm run package:win |
Windows: NSIS installer + portable exe, x64 |
Both package: scripts run npm run build first, so a type error stops them
before electron-builder starts. Artefacts land in release/, which is ignored.
Adding a language. Copy src/shared/locales/en.ts, translate the values,
and add the code to LOCALES in src/shared/i18n.ts. TypeScript then names any
key you missed, and the test suite checks that the placeholders ({time},
{version}) match English — a translated placeholder is the mistake that
silently prints braces at a user.
Architecture in one paragraph. The main process owns the truth: it talks to
Factorial's GraphQL API, keeps one attendance store, and pushes snapshots to the
renderer over a ten-function contextBridge. The renderer draws and never
decides — it has no Node, no require, and no way to reach the network. The
pieces that can be tested without Electron are deliberately kept free of it and
are, which is why the suite runs anywhere.
Tests. 517 of them, no Electron runtime required. They cover the time reconstruction, the store's optimistic updates and rollbacks, the IPC codec, the widget's five states, and the platform-dependent decisions — the last of those take their inputs as arguments precisely so that, say, the Windows autostart path can be checked from a Mac.
CI. .github/workflows/build.yml runs tests and typecheck on every push and
pull request. Tagging v* builds both platforms and attaches the files to a
GitHub release; macOS runners are billed at ten times the Linux rate, so the
builds do not run on every push.
Releasing. One command produces the version commit and its tag, and pushing the tag is what starts a release build:
npm version patch # or minor / major
git push origin main --follow-tags
Pushing to main on its own is not a release; only a v* tag is. The full
procedure, how to pick the number, and the handful of things that have gone
wrong before are in docs/RELEASING.md.
docs/DESIGN.md— architecture and the full API reference, verified against the live API. Where anything disagrees, this wins.docs/api-discovery.md— how the Factorial API was mapped out, and how to find a query nobody has needed yet.docs/RELEASING.md— how to cut a release, when not to, and why signing and notarization are not optional on macOS.
