Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 34 additions & 21 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Build StandUp
name: Build StandUp v2

on:
push:
branches:
- main
- v2
tags:
- "v*"
pull_request:
Expand All @@ -14,18 +15,33 @@ permissions:

jobs:
verify:
name: Verify
runs-on: windows-latest
name: Verify web and Rust
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-26
target: aarch64-apple-darwin
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Select stable Rust
shell: bash
run: |
rustup update stable
rustup default stable
rustup target add "${{ matrix.target }}"
- run: npm ci
- run: npm test
- run: npm run typecheck
- run: npm run build
- run: npm run build:web
- run: cargo test --manifest-path src-tauri/Cargo.toml --target ${{ matrix.target }}

package:
name: Package ${{ matrix.name }}
Expand All @@ -34,36 +50,33 @@ jobs:
fail-fast: false
matrix:
include:
- name: Windows
- name: Windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
command: npm run dist:win
artifact: release/*.exe
- name: macOS
os: macos-latest
artifact: src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
- name: macOS-Apple-Silicon
os: macos-26
target: aarch64-apple-darwin
command: npm run dist:mac
artifact: release/*.dmg
artifact: src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Select stable Rust
shell: bash
run: |
rustup update stable
rustup default stable
rustup target add "${{ matrix.target }}"
- run: npm ci
- run: ${{ matrix.command }}
env:
CSC_IDENTITY_AUTO_DISCOVERY: "false"
- name: Verify macOS app bundle
if: matrix.name == 'macOS'
shell: bash
run: |
set -euo pipefail
app="release/mac-universal/StandUp.app"
helper="$app/Contents/Resources/app.asar.unpacked/node_modules/get-windows/main"
test -x "$app/Contents/MacOS/StandUp"
test -x "$helper"
codesign --verify --deep --strict --verbose=2 "$app"
file "$app/Contents/MacOS/StandUp" | grep -q "universal binary"
APPLE_SIGNING_IDENTITY: "-"
- uses: actions/upload-artifact@v4
with:
name: StandUp-${{ matrix.name }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules/
dist/
release/
src-tauri/target/
src-tauri/gen/
coverage/
tmp/
*.log
Expand Down
134 changes: 64 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,90 @@
# StandUp

StandUp is a private, activity-aware desktop reminder for Windows and macOS.
After a configurable amount of active keyboard and mouse time, a silent animated
character slides in from the bottom-right of the primary display for eight
seconds. The reminder is always on top, does not take focus, and is completely
click-through.

## Features

- 45-minute default reminder interval, with 15/30/45/60/90/120-minute choices
- Configurable 1/2/3/5/10-minute idle cutoff, defaulting to 3 minutes
- Automatic pauses while idle, locked, suspended, or manually paused
- Tray/menu-bar status, 30-minute and 1-hour pauses, preview, settings, and quit
- Foreground-only application blacklist
- Launch at login, enabled by default in packaged builds
- Local-only settings with no account, network calls, analytics, or activity log
- Transparent, silent, focus-safe reminder over normal and full-screen apps
StandUp is a private, offline-first desktop companion by ioiostudio. It counts
active computer time and shows a silent, click-through animated reminder to
stand and move.

The `v2` branch is migrating StandUp from Electron to Tauri 2. The Electron
source remains temporarily as a parity reference; v2 builds use the Rust
backend in `src-tauri`.

## v2 targets

- Windows 10/11 x64 with an NSIS installer
- Apple Silicon macOS 12 or newer with a DMG

StandUp v2 does not contain an updater, analytics, telemetry, accounts, ads,
remote assets, HTTP client, localhost server, or runtime network calls.

## Current v2 foundation

- 45-minute default reminder interval with 15/30/45/60/90/120-minute choices
- Configurable 1/2/3/5/10-minute idle cutoff
- Rust-owned in-memory timer and local settings
- Launch at login enabled by default
- Tray/menu-bar controls for pause, resume, preview, settings, and quit
- Fresh, always-on-top, non-focusable, click-through popup for every reminder,
shown only after the GIF has loaded and decoded
- Nine reminder positions and automatic/specific monitor selection
- Local creative prompt builder for making a custom GIF with any AI provider
- Secure native custom-GIF picker with validation, sanitizing re-encode, private
storage, preview, and reset to the built-in animation
- Silent-by-default system beep, one-second soft chime, and two-second gentle
chime with sound testing
- Per-window Tauri capabilities and a strict local-only CSP
- Single-instance behavior

See [V2_MIGRATION.md](./V2_MIGRATION.md) for the remaining work before v2 is
release-ready, including foreground-app blacklisting and platform smoke tests.

## Development

Requirements:
Windows prerequisites:

- Node.js 22 or newer
- npm
- Rust stable with the MSVC toolchain
- Microsoft C++ Build Tools with Desktop development with C++
- Microsoft Edge WebView2

Install and run:
Install and validate:

```powershell
npm install
npm start
npm test
npm run typecheck
npm run build:web
cargo test --manifest-path src-tauri/Cargo.toml
```

`npm start` builds the TypeScript main process and Vite renderer, then launches
Electron. Login launch is intentionally not registered from a development build.
Run the Tauri development application:

Useful commands:
```powershell
npm run dev
```

Build installers:

```powershell
npm test
npm run typecheck
npm run build
npm run dist:win
npm run dist:mac
```

The macOS DMG must be produced on macOS. The GitHub Actions workflow builds the
Windows x64 installer and a universal macOS DMG on native runners.

## Application behavior

StandUp samples system idle time once per second. Active time is accumulated
only while the idle duration is below the configured threshold. A long scheduler
gap, sleep, lock, or suspend is never treated as active time.

Blacklisted application paths are compared only with the foreground
application. Time inside a blacklisted application still counts; if the reminder
becomes due there, it waits until focus moves elsewhere. Failure to inspect the
foreground application fails open, so the reminder remains available.

Changing the reminder interval resets current progress. Changing the idle
threshold applies immediately. Timer progress and manual pause state are kept in
memory and start fresh after the app restarts. Preferences and blacklist entries
are stored in Electron's per-user application data directory.

## Personal installer warnings

The generated installers are intentionally unsigned:

- **Windows:** Microsoft Defender SmartScreen may show an “unrecognized app”
warning. Use **More info → Run anyway** only for an artifact you built or
downloaded from your own trusted workflow.
- **macOS:** Gatekeeper may block the first launch. In Finder, Control-click
StandUp, choose **Open**, then confirm. System Settings may also offer an
**Open Anyway** action under Privacy & Security.
The macOS command must run on Apple Silicon macOS. GitHub Actions uses native
Windows x64 and Apple Silicon macOS runners.

Signing and notarization credentials are not included.
## Installer warnings

## Privacy and permissions
Development installers are unsigned:

StandUp does not store activity history, window titles, URLs, or usage
statistics. Foreground-app detection requests only the owning application path.
On macOS, Accessibility and Screen Recording permission checks are disabled
because the blacklist does not need window titles or browser data.
- Windows SmartScreen may display an “unrecognized app” warning.
- macOS Gatekeeper may require Control-clicking the app, choosing **Open**, and
confirming the first launch.

## Release verification
Production distribution should use Windows code signing and Apple Developer ID
signing/notarization.

Before distributing a build:
## Privacy

1. Run `npm test`, `npm run typecheck`, and `npm run build`.
2. Confirm tray controls, settings persistence, pause/resume, and preview.
3. Confirm the reminder appears on the primary display without taking focus and
that clicks pass through it.
4. Confirm a due reminder is deferred in a blacklisted foreground app and appears
after switching away.
5. Confirm login launch from an installed build.
Preferences are stored only in the operating system’s per-user application-data
directory. Active-time progress remains in memory and resets after the app
restarts. The default animation is packaged inside the application.
66 changes: 66 additions & 0 deletions V2_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# StandUp v2 migration

StandUp v2 is the Tauri 2 rewrite on the `v2` branch. The Electron source is
temporarily retained as a parity reference, but it is no longer part of the v2
build.

## Decisions locked for v2

- Windows x64 and Apple Silicon macOS are the only release targets.
- Runtime behavior is fully offline. There is no updater, analytics,
telemetry, account, ad, remote asset, HTTP, or localhost plugin.
- The built-in StandUp GIF remains packaged with the app and is always the
fallback.
- Settings are stored locally in the Tauri application-data directory.
- Launch at login defaults to enabled.
- The settings and reminder webviews have separate, minimal capabilities.
- The reminder is a non-focusable, always-on-top, click-through window.

## Implemented in the first slice

- Tauri 2 project and native Windows/macOS bundle configuration.
- Rust-owned settings persistence and timer state.
- Active/idle accumulation through the platform idle-time API.
- Pause/resume commands, single-instance behavior, launch at login, and tray
controls.
- Fresh click-through window for every reminder with a renderer-ready
checkpoint, custom-GIF fallback, and guarded eight-second lifecycle.
- Work-area-aware nine-position placement and monitor selection with primary
monitor fallback.
- Strict local-only CSP and no updater/network dependencies.
- Settings UI for timing, position, monitor, sound choice, local AI prompt
construction, preview, and reset-to-default.
- Native system beep and bundled original one- and two-second offline chimes.
- Secure custom GIF picker with bounded reads, GIF signature and frame
validation, decode/re-encode sanitization, metadata stripping, atomic private
storage, preview, corruption fallback, and reset.
- Live tray status showing remaining time, pause time, due state, or popup state.
- Native Rust unit tests plus existing TypeScript tests.

## Remaining before v2 is release-ready

- Foreground application picker/detection and deferred blacklist reminders.
- Explicit lock/suspend event handling on both platforms.
- Display hot-plug handling while the popup is already visible.
- Native full-screen behavior verification on macOS.
- Signed/notarized macOS distribution and signed Windows distribution.
- End-to-end installer smoke tests and malformed-GIF corpus tests.

## Local prerequisites

Tauri development on Windows requires Rust, WebView2, and the Microsoft C++
Build Tools with the Desktop development with C++ workload.

```powershell
npm install
npm run dev
```

Run checks with:

```powershell
npm test
npm run typecheck
npm run build:web
cargo test --manifest-path src-tauri/Cargo.toml
```
Loading
Loading