Skip to content
Merged
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
8 changes: 7 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build / Test

on:
push:
branches: [master, "rewrite/**", "claude/**"]
branches: [master, 'rewrite/**', 'claude/**', 'feat/**']
pull_request:
branches: [master]

Expand All @@ -14,6 +14,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Node.js
uses: actions/setup-node@v4
Expand Down Expand Up @@ -78,6 +80,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Node.js
uses: actions/setup-node@v4
Expand Down Expand Up @@ -120,6 +124,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Node.js
uses: actions/setup-node@v4
Expand Down
30 changes: 2 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Pomotroid provides many themes. It's also theme-able, allowing you to customize

![Screenshots of Pomotroid using various themes](./.github/images/pomotroid_themes-preview--914x219.png)

Visit the [theme documentation](./docs/themes/themes.md) to view the full list of official themes and for instruction on creating your own.
See [THEMES.md](./THEMES.md) for the full theme list and instructions on creating your own.

## Install

Expand Down Expand Up @@ -85,33 +85,7 @@ appget install pomotroid

## Custom Themes

Pomotroid supports custom themes defined as JSON files placed in the app's configuration directory:

- **Linux**: `~/.config/pomotroid/themes/`
- **macOS**: `~/Library/Application Support/pomotroid/themes/`
- **Windows**: `%APPDATA%\pomotroid\themes\`

Custom themes are hot-reloaded automatically — no restart required.

Each theme file must follow this format:

```json
{
"name": "My Theme",
"colors": {
"--color-long-round": "#c75000",
"--color-short-round": "#417505",
"--color-focus-round": "#b01c2e",
"--color-background": "#2f384b",
"--color-background-light": "#3e4a5d",
"--color-foreground": "#d7e1f4",
"--color-foreground-darker": "#a3aec4",
"--color-accent": "#ff6347",
"--color-accent-extra": "#f0c050",
"--color-gradient": "#1e2430"
}
}
```
Pomotroid supports user-created themes with automatic hot-reload — no restart required. See [THEMES.md](./THEMES.md) for directory paths, the full color reference, and a step-by-step guide.

## WebSocket API

Expand Down
85 changes: 85 additions & 0 deletions THEMES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Pomotroid Themes

Pomotroid ships with 18 built-in themes and supports an unlimited number of user-created custom themes. Custom themes are hot-reloaded — no restart required.

## Built-in themes

Andromeda, Ayu, City Lights, Dracula, D.Va, GitHub, Graphite, Gruvbox, Monokai, Nord, One Dark, Pomotroid (default), Popping and Locking, Rose Pïne Dawn, Solarized Light, Spandex, Synthwave, Tokyo Night

## Creating a custom theme

A theme is a single `.json` file with a name and a set of hex color values.

### 1. Create the themes directory

The directory is not created automatically. Make it once:

**Linux**
```sh
mkdir -p ~/.local/share/com.splode.pomotroid/themes
```

**macOS**
```sh
mkdir -p ~/Library/Application\ Support/com.splode.pomotroid/themes
```

**Windows** (PowerShell)
```powershell
New-Item -ItemType Directory -Force "$env:APPDATA\com.splode.pomotroid\themes"
```

### 2. Create a theme file

Copy the template below into a `.json` file in the themes directory. The filename can be anything — the displayed name comes from the `name` field.

```json
{
"name": "My Theme",
"colors": {
"--color-focus-round": "#ff4e4d",
"--color-short-round": "#05ec8c",
"--color-long-round": "#0bbddb",
"--color-background": "#2f384b",
"--color-background-light": "#3d4457",
"--color-background-lightest": "#9ca5b5",
"--color-foreground": "#f6f2eb",
"--color-foreground-darker": "#c0c9da",
"--color-foreground-darkest": "#dbe1ef",
"--color-accent": "#05ec8c"
}
}
```

### 3. Select your theme

Open **Settings → Appearance**. Your theme appears in the picker with a **Custom** badge. Select it for the Light or Dark slot (or both).

## Color reference

| Key | Used for |
|-----|----------|
| `--color-focus-round` | Work round indicator — dial arc, round dot |
| `--color-short-round` | Short break indicator |
| `--color-long-round` | Long break indicator |
| `--color-background` | Main window background |
| `--color-background-light` | Sidebar, cards, elevated surfaces |
| `--color-background-lightest` | Borders, dividers, subtler surfaces |
| `--color-foreground` | Primary text |
| `--color-foreground-darker` | Secondary text, labels |
| `--color-foreground-darkest` | Tertiary text, placeholders |
| `--color-accent` | Highlighted elements, active states |

All values must be CSS hex colors (`#rrggbb` or `#rrggbbaa`).

## Hot-reload

Pomotroid watches the themes directory while running. Saving a file — including edits to an existing theme — updates the Appearance picker within half a second. There is no need to reopen settings or restart the app.

## Overriding a built-in theme

If a custom theme's `name` exactly matches a built-in theme name (case-insensitive), it replaces that theme in the picker. This lets you tweak an existing theme without adding a new entry to the list.

## Using bundled themes as a starting point

The bundled theme files are a useful reference. You can find them in the source repository under [`static/themes/`](./static/themes/).
2 changes: 2 additions & 0 deletions openspec/changes/build-version/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-27
96 changes: 96 additions & 0 deletions openspec/changes/build-version/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Context

The About section of the settings window currently hardcodes `const VERSION = '1.0.0'` in `AboutSection.svelte`. This string is entirely disconnected from the actual build — there is no way to determine which commit a running binary was compiled from. Two binaries built from different commits show the same version string.

`build.rs` already exists (`src-tauri/build.rs`) and contains only `tauri_build::build()`. It is the correct and idiomatic place to perform git introspection at compile time.

The CI workflow uses `actions/checkout@v4` with default depth 1 (shallow clone), which prevents `git describe` from finding ancestor tags and computing commit distances. Full history is needed for commit count.

## Goals / Non-Goals

**Goals:**
- Every compiled binary carries a unique, traceable version string baked in at compile time.
- The string conforms to semver: `1.0.0-dev.{n}+{short-sha}` for dev builds, `1.0.0+{short-sha}` for release builds.
- Short SHA (7 chars) is displayed in Settings → About for readability.
- Full SHA is logged to the log file on startup for grep/traceability.
- `tauri.conf.json` remains the single source of truth for the base version number.
- Zero runtime overhead — the string is a compile-time constant.

**Non-Goals:**
- Automatic version bumping or tag management.
- Exposing branch name in the version string (too noisy, changes often).
- A separate build number counter outside of git commit distance.
- Changing the `tauri.conf.json` version value at build time.

## Decisions

### D1: `build.rs` as the injection point

**Decision**: Extend `src-tauri/build.rs` to run `git describe`, parse its output, and emit `cargo:rustc-env=APP_BUILD_VERSION=<string>`. The string is accessed in Rust via the compile-time macro `env!("APP_BUILD_VERSION")`.

**Alternatives considered**:
- *Vite `define` plugin*: Would work for the frontend, but the string would not be available in Rust (e.g., for startup logging). Split injection in two places introduces drift risk.
- *Runtime environment variable*: Requires the launching environment to set the variable; doesn't work for distributed binaries.
- *Patching `tauri.conf.json` before build*: Modifies a tracked file; requires cleanup; pollutes git diff.

`build.rs` is the correct Rust idiom. `env!()` is zero-cost. One source, accessible everywhere.

### D2: `git describe --tags --long --always --dirty` as the data source

**Decision**: Use `git describe --tags --long --always --dirty`.

- `--tags`: Match any tag (not just annotated).
- `--long`: Always output `{tag}-{count}-g{sha}` format, even when count is 0 (on the exact tag). Uniform parsing regardless of release/dev state.
- `--always`: Fall back to bare SHA if no tags exist at all.
- `--dirty`: Append `-dirty` if working tree has uncommitted changes.

Output format: `v1.0.0-80-g20b2d87[-dirty]`

Parsed into semver:
| count | dirty | result |
|-------|-------|--------|
| 0 | no | `1.0.0+20b2d87` |
| 0 | yes | `1.0.0+20b2d87.dirty` |
| N > 0 | no | `1.0.0-dev.N+20b2d87` |
| N > 0 | yes | `1.0.0-dev.N+20b2d87.dirty` |

The `g` prefix from git describe is stripped; build metadata contains a clean 7-char hex SHA.

**Fallback**: If `git describe` fails (no git binary, detached non-tagged repo), `build.rs` falls back to `{base_version}+unknown`. Base version is read from `tauri.conf.json` at build time.

### D3: `cargo:rerun-if-changed` triggers

**Decision**: Emit two rerun triggers:
```
cargo:rerun-if-changed=.git/HEAD
cargo:rerun-if-changed=.git/refs/
```

Without these, Cargo caches `build.rs` output and the version string becomes stale after commits. `.git/HEAD` changes on every commit and checkout. `.git/refs/` changes when tags are created or moved.

### D4: Short SHA in UI, full SHA in logs

**Decision**: The IPC command `app_version()` returns the version string with 7-char SHA (e.g., `1.0.0-dev.80+20b2d87`). A separate startup log line in `lib.rs` records the full 40-char SHA.

The full SHA is captured via `git rev-parse HEAD` in `build.rs` and emitted as `APP_BUILD_SHA` alongside `APP_BUILD_VERSION`.

### D5: New `app_version` Tauri command (no settings involvement)

**Decision**: Expose the build version via a new read-only command `app_version() -> &'static str`. It is not a setting; it does not go through the settings system. The frontend calls it once on About section mount.

### D6: CI checkout depth

**Decision**: Add `fetch-depth: 0` to all three `actions/checkout@v4` steps in `build.yml`. This gives the CI full tag history, enabling commit count in CI-produced artifacts. Without it, `git describe` falls back to the SHA-only path and the commit count is absent.

## Risks / Trade-offs

- **`-dirty` in release binaries**: If someone builds a release from a dirty working tree, the binary is labeled `dirty`. This is accurate and intentional — it's a signal, not an error.
- **CI build time**: `fetch-depth: 0` fetches full history. On a project with few commits this is negligible. Worth monitoring if the repo grows very large.
- **No git binary at build time**: Unlikely in any normal dev or CI environment, but handled gracefully by the fallback to `+unknown`.
- **Shallow clone outside CI**: If someone clones with `--depth 1` locally and builds, they get `+unknown` or a bare SHA. Acceptable.

## Migration Plan

No runtime migration required. The change is entirely at compile time and in the UI display layer. No database changes, no settings changes, no breaking IPC changes. The new `app_version` command is additive.

Rollout: merge to master, next build picks up the new version string automatically.
33 changes: 33 additions & 0 deletions openspec/changes/build-version/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Why

The About screen currently shows a hardcoded `VERSION = '1.0.0'` string that provides no information about the exact build being run. When debugging issues, there is no way to tell which commit a binary was compiled from. A semver-conformant build identifier — baked in at compile time via `build.rs` — gives every binary a unique, traceable version string at zero runtime cost.

## What Changes

- `build.rs` is extended to call `git describe` at compile time, parse the output, and emit a `APP_BUILD_VERSION` environment variable baked into the binary.
- A new Tauri command `app_version()` exposes the build version string to the frontend.
- `AboutSection.svelte` replaces its hardcoded `VERSION` constant with an IPC call to `app_version()`.
- The full commit SHA is logged to the log file on startup, while the short SHA is displayed in the UI.
- The CI workflow gains `fetch-depth: 0` so commit counts are available in CI artifacts.
- `Cargo.toml` version is aligned to `1.0.0` to match `tauri.conf.json`.

## Capabilities

### New Capabilities

- `build-version`: Compile-time build version string derived from `git describe`, formatted as semver with pre-release and build metadata. Exposed via IPC and displayed in Settings → About.

### Modified Capabilities

*(none — no existing spec-level requirements change)*

## Impact

- **`src-tauri/build.rs`**: New git describe logic + `cargo:rerun-if-changed` triggers.
- **`src-tauri/src/commands.rs`**: New `app_version` command.
- **`src-tauri/src/lib.rs`**: Log full SHA on startup.
- **`src/lib/ipc/index.ts`**: New `appVersion()` wrapper.
- **`src/lib/components/settings/sections/AboutSection.svelte`**: Replace hardcoded version with IPC-fetched value.
- **`.github/workflows/build.yml`**: Add `fetch-depth: 0` to all checkout steps.
- **`src-tauri/Cargo.toml`**: Align version to `1.0.0`.
- No new dependencies; no DB migrations; no settings changes.
64 changes: 64 additions & 0 deletions openspec/changes/build-version/specs/build-version/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
## ADDED Requirements

### Requirement: Build version string baked in at compile time
The system SHALL compute a semver-conformant build version string during `cargo build` by invoking `git describe --tags --long --always --dirty` in `build.rs` and emitting the result as the compile-time environment variable `APP_BUILD_VERSION`. The full commit SHA SHALL be emitted separately as `APP_BUILD_SHA` via `git rev-parse HEAD`.

#### Scenario: Dev build (commits since last tag)
- **WHEN** the binary is compiled from a commit that is N > 0 commits after the most recent tag
- **THEN** `APP_BUILD_VERSION` SHALL be `{base}-dev.{N}+{short-sha}` (e.g. `1.0.0-dev.80+20b2d87`)

#### Scenario: Release build (on exact tag)
- **WHEN** the binary is compiled from a commit that is exactly at a tag
- **THEN** `APP_BUILD_VERSION` SHALL be `{base}+{short-sha}` (e.g. `1.0.0+20b2d87`)

#### Scenario: Dirty working tree
- **WHEN** the binary is compiled with uncommitted changes present
- **THEN** `APP_BUILD_VERSION` SHALL include a `.dirty` suffix in the build metadata (e.g. `1.0.0-dev.80+20b2d87.dirty`)

#### Scenario: No git history or no tags
- **WHEN** `git describe` fails (no git binary, no tags, detached shallow clone)
- **THEN** `APP_BUILD_VERSION` SHALL fall back to `{base}+unknown` where `{base}` is read from `tauri.conf.json`

#### Scenario: Incremental rebuild after a new commit
- **WHEN** a new commit is made and `cargo build` is run again
- **THEN** the build script SHALL re-execute and produce an updated `APP_BUILD_VERSION` reflecting the new commit

---

### Requirement: Build version exposed via IPC command
The system SHALL provide a Tauri command `app_version` that returns `APP_BUILD_VERSION` as a `&'static str`. This command SHALL be callable by the frontend at any time and returns the compile-time-baked version string.

#### Scenario: Command returns baked version
- **WHEN** the frontend invokes `app_version`
- **THEN** the response SHALL be the `APP_BUILD_VERSION` string compiled into the binary

---

### Requirement: Build version displayed in Settings → About
The system SHALL display the build version string in Settings → About in place of the previously hardcoded version constant. The displayed string SHALL use the short-SHA form (7 characters).

#### Scenario: Version displayed on About mount
- **WHEN** the user opens Settings → About
- **THEN** the version line SHALL show the full semver build string (e.g. `1.0.0-dev.80+20b2d87`)

#### Scenario: Version string is never empty
- **WHEN** `app_version` returns successfully
- **THEN** the About section SHALL display the returned string; if the call fails, it SHALL fall back to displaying the base version from `tauri.conf.json`

---

### Requirement: Full commit SHA logged on startup
The system SHALL log the full 40-character commit SHA (from `APP_BUILD_SHA`) at INFO level during application startup, alongside the build version string.

#### Scenario: Startup log includes full SHA
- **WHEN** the application starts
- **THEN** the log file SHALL contain an INFO entry with both the build version string and the full commit SHA (e.g. `[app] version=1.0.0-dev.80+20b2d87 sha=20b2d87173870d939002efe84fddff2e944eabd6`)

---

### Requirement: CI workflow fetches full git history
The CI build workflow SHALL use `fetch-depth: 0` on all checkout steps so that `git describe` has access to full tag history and can compute commit distances in CI-produced artifacts.

#### Scenario: CI artifact carries commit count
- **WHEN** a binary is built in CI from a commit that is N commits after the last tag
- **THEN** the binary's `APP_BUILD_VERSION` SHALL include the commit count N (e.g. `1.0.0-dev.80+abc1234`)
Loading