diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..b8f04f9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,69 @@
+name: Bug report
+description: Something in the Deck starter doesn't work as expected.
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for filing a bug. Deck is a starter — if the issue is in your
+ own app code after forking, this tracker probably can't help. For
+ security issues, do **not** file here; see SECURITY.md.
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: What did you expect, and what happened instead?
+ placeholder: A clear description of the bug.
+ validations:
+ required: true
+ - type: textarea
+ id: repro
+ attributes:
+ label: Steps to reproduce
+ description: Minimal steps. A small diff or snippet helps a lot.
+ placeholder: |
+ 1. `cargo run --features overlay`
+ 2. Click ...
+ 3. See ...
+ validations:
+ required: true
+ - type: dropdown
+ id: os
+ attributes:
+ label: Operating system
+ options:
+ - macOS (Apple Silicon)
+ - macOS (Intel)
+ - Linux (X11)
+ - Linux (Wayland)
+ - Other (note in description)
+ validations:
+ required: true
+ - type: dropdown
+ id: features
+ attributes:
+ label: Feature flags
+ description: Which build were you running?
+ options:
+ - default
+ - tray
+ - overlay
+ - tray,overlay
+ validations:
+ required: true
+ - type: input
+ id: gpui-commit
+ attributes:
+ label: GPUI commit
+ description: The `gpui` commit from Cargo.lock (`grep -A2 'name = "gpui"' Cargo.lock`).
+ placeholder: "86effffd..."
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Logs / backtrace
+ description: Any terminal output or `RUST_BACKTRACE=1` trace.
+ render: shell
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..f948307
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Security vulnerability
+ url: https://github.com/hellno/deck/security/advisories/new
+ about: Report security issues privately — do not open a public issue. See SECURITY.md.
+ - name: Question or idea
+ url: https://github.com/hellno/deck/discussions
+ about: For open-ended questions and "how do I fork this?" — use Discussions (if enabled).
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..bec58c3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,35 @@
+name: Feature request
+description: Suggest an improvement to the Deck starter.
+labels: ["enhancement"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Deck is kept deliberately small (~700 lines). The best additions make it
+ a better foundation for everyone who forks it — not your app's domain
+ logic. When in doubt, open this first before a PR.
+ - type: textarea
+ id: problem
+ attributes:
+ label: What problem does this solve?
+ description: The friction or gap you're hitting in the starter.
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposed change
+ description: What you'd like to see. Keep "kept small" in mind.
+ validations:
+ required: true
+ - type: dropdown
+ id: scope
+ attributes:
+ label: Platform scope
+ options:
+ - Both macOS and Linux
+ - macOS only
+ - Linux only
+ - Docs / tooling only
+ validations:
+ required: false
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..d62719a
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,26 @@
+
+
+## What & why
+
+
+
+## Definition of Done
+
+
+
+- [ ] `just ci` is green (fmt + clippy on default / `tray` / `overlay` / `tray,overlay` + tests).
+- [ ] No new or changed dependencies in `Cargo.toml` / `Cargo.lock` (or maintainer approval is linked).
+- [ ] GPUI pins changed only via `just bump-gpui` (not hand-edited), if applicable.
+- [ ] Docs updated if behavior or setup changed (`README.md` / `docs/`).
+
+## Platforms tested
+
+
+
+- [ ] macOS
+- [ ] Linux (note X11 / Wayland)
+- Features exercised:
+
+## Notes for reviewers
+
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..119e4da
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,25 @@
+# Dependabot configuration for Deck.
+#
+# NOTE: the GPUI stack (gpui / gpui_platform / gpui-component / gpui-component-assets)
+# is pinned to exact GIT commits and is bumped ONLY via `just bump-gpui`. Dependabot's
+# `cargo` ecosystem proposes updates for registry (crates.io) deps and the lockfile;
+# it does NOT touch git dependencies, so those pins stay under `just bump-gpui` control.
+version: 2
+updates:
+ # crates.io dependencies (serde, serde_json, directories, tray-icon, objc2*, raw-window-handle).
+ - package-ecosystem: "cargo"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "deps"
+
+ # GitHub Actions (keeps the SHA-pinned actions in ci.yml current).
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 5
+ commit-message:
+ prefix: "ci"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c06bfd8..d470dc3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,12 +1,13 @@
name: CI
-# Builds the Deck on macOS *and* Linux so cross-platform stays honest.
-# (GPUI renders with Metal on macOS and Vulkan via wgpu on Linux.)
+# Deck is a cargo-generate TEMPLATE: its tracked source contains {{ }} Liquid tokens
+# (see cargo-generate.toml `include`) and so does NOT build directly. Every job below
+# therefore GENERATES a throwaway project from the template first, then builds / lints /
+# tests THAT. Editing the template? Read the "cargo-generate template" note in CLAUDE.md.
#
-# COST: free. Standard GitHub-hosted runners (macos-latest, ubuntu-latest) are
-# free + unlimited on PUBLIC repos — the macOS 10x multiplier only burns quota
-# on PRIVATE repos. If you ever make this repo private, gate the `macos` job to
-# tags to avoid eating your minutes, e.g. add to that job:
+# COST: free. Standard GitHub-hosted runners (macos-latest, ubuntu-latest) are free +
+# unlimited on PUBLIC repos — the macOS 10x multiplier only burns quota on PRIVATE repos.
+# If you ever make this repo private, gate the `macos` job to tags to avoid eating minutes:
# if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/v')
# (Don't switch to "*-large" runners — those are billed even on public repos.)
@@ -21,9 +22,13 @@ on:
env:
CARGO_TERM_COLOR: always
-# Cancel a still-running CI for a branch when a newer commit is pushed. Deck's first
-# git-gpui build is slow, so when an agent pushes a fix-up the stale run is killed and
-# the runner starts on the latest commit immediately (faster feedback, fewer minutes).
+# Least-privilege GITHUB_TOKEN: this workflow only checks out, generates, builds, lints,
+# tests, and runs cargo-deny — it needs read access to repo contents and nothing else.
+permissions:
+ contents: read
+
+# Cancel a still-running CI for a branch when a newer commit is pushed (faster feedback,
+# fewer minutes — the generated project's first git-gpui build is slow).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
@@ -32,86 +37,87 @@ jobs:
macos:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v4
- # No toolchain version here on purpose: rust-toolchain.toml is the single
- # source of truth. rustup (preinstalled on GitHub runners) auto-installs the
- # pinned version and its clippy/rustfmt components on the first cargo call.
- - uses: Swatinem/rust-cache@v2
- # --locked everywhere: reproducibility lives in the committed Cargo.lock (it
- # pins exact git gpui commits). --locked fails CI on a stale lock; `just
- # bump-gpui` rewrites+commits the lock, so this never fights the bump flow.
- - run: cargo build --locked
- - run: cargo build --locked --features tray
- - run: cargo clippy --locked --all-targets --features tray -- -D warnings
- # why: lint the DEFAULT/shipping feature config too — CI only ever linted
- # the tray build, so a clippy regression on the default build slipped through.
- - run: cargo clippy --locked --all-targets -- -D warnings
- # Overlay feature (macOS: real transparent PopUp window + objc2 panel harden).
- - run: cargo build --locked --features overlay
- - run: cargo clippy --locked --all-targets --features overlay -- -D warnings
- - run: cargo clippy --locked --all-targets --features tray,overlay -- -D warnings
- # why: run the test suite — the command_palette fuzzy-match tests existed but
- # CI never executed them, so an agent could break scoring invisibly.
- - run: cargo test --locked
- # why: the overlay unit tests (OverlayState/anchor math, JobStatus + HudState
- # reducers) are gated behind --features overlay, so run them explicitly or they
- # never execute. The reducers/anchor math are platform-independent — runs on Linux too.
- - run: cargo test --locked --features overlay
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ # Prebuilt cargo-generate (fast, deterministic — no compile). SHA-pinned;
+ # Dependabot (github-actions) keeps it current.
+ - uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
+ with:
+ tool: cargo-generate
+ # Render the template into a sibling dir (NOT inside the repo — a nested cargo
+ # project confuses cargo, see rust-lang/cargo#9922). --silent uses the placeholder
+ # defaults (bundle_qualifier=com, bundle_org=example).
+ - name: Generate a project from the template
+ run: cargo generate --path . --name deck-ci --silent --vcs none --destination "$RUNNER_TEMP/gen"
+ # No toolchain pin here: rust-toolchain.toml (copied into the generated project) is
+ # the single source of truth; rustup auto-installs it on the first cargo call.
+ - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
+ with:
+ workspaces: ${{ runner.temp }}/gen/deck-ci
+ # No --locked: the generated Cargo.lock's root entry is renamed at generate time, so
+ # cargo refreshes just that on first build (the git-gpui deps stay pinned).
+ - name: Build, lint, and test the generated project
+ working-directory: ${{ runner.temp }}/gen/deck-ci
+ run: |
+ cargo build
+ cargo build --features tray
+ cargo clippy --all-targets -- -D warnings
+ cargo clippy --all-targets --features tray -- -D warnings
+ cargo clippy --all-targets --features overlay -- -D warnings
+ cargo clippy --all-targets --features tray,overlay -- -D warnings
+ cargo test
+ cargo test --features overlay
linux:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- # No toolchain version here on purpose: rust-toolchain.toml is the single
- # source of truth. rustup (preinstalled on GitHub runners) auto-installs the
- # pinned version and its clippy/rustfmt components on the first cargo call.
- - uses: Swatinem/rust-cache@v2
- # System libraries GPUI needs on Linux (X11 + Wayland + Vulkan + fonts),
- # plus GTK/appindicator for the optional tray feature.
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ # System libraries GPUI needs on Linux (X11 + Wayland + Vulkan + fonts), plus
+ # GTK/appindicator for the optional tray feature.
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential pkg-config \
libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev \
- libwayland-dev libxkbcommon-dev \
+ libwayland-dev \
libvulkan-dev \
libfontconfig1-dev libfreetype6-dev \
libssl-dev \
libgtk-3-dev libayatana-appindicator3-dev libxdo-dev
- # why: formatting is OS-independent, so gate `cargo fmt --check` ONCE on the
- # cheaper Linux runner instead of the 10x-billed macOS one. Deck is already
- # fmt-clean, so this lands green with no baseline-format commit.
- - run: cargo fmt --all --check
- # --locked everywhere: reproducibility lives in the committed Cargo.lock (it
- # pins exact git gpui commits). --locked fails CI on a stale lock; `just
- # bump-gpui` rewrites+commits the lock, so this never fights the bump flow.
- - run: cargo build --locked
- - run: cargo build --locked --features tray
- - run: cargo clippy --locked --all-targets --features tray -- -D warnings
- # why: lint the DEFAULT/shipping feature config too — CI only ever linted
- # the tray build, so a clippy regression on the default build slipped through.
- - run: cargo clippy --locked --all-targets -- -D warnings
- # Overlay feature (Linux: compile-only no-op path — no LayerShell in v1).
- - run: cargo build --locked --features overlay
- - run: cargo clippy --locked --all-targets --features overlay -- -D warnings
- # why: run the test suite — the command_palette fuzzy-match tests existed but
- # CI never executed them, so an agent could break scoring invisibly.
- - run: cargo test --locked
- # why: the overlay unit tests (OverlayState/anchor math, JobStatus + HudState
- # reducers) are gated behind --features overlay, so run them explicitly or they
- # never execute. The reducers/anchor math are platform-independent — runs on Linux too.
- - run: cargo test --locked --features overlay
+ - uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
+ with:
+ tool: cargo-generate
+ - name: Generate a project from the template
+ run: cargo generate --path . --name deck-ci --silent --vcs none --destination "$RUNNER_TEMP/gen"
+ - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
+ with:
+ workspaces: ${{ runner.temp }}/gen/deck-ci
+ # Formatting is OS-independent, so gate `cargo fmt --check` once on the cheaper
+ # Linux runner. The overlay feature is a compile-only no-op on Linux (no LayerShell).
+ - name: Format-check, build, lint, and test the generated project
+ working-directory: ${{ runner.temp }}/gen/deck-ci
+ run: |
+ cargo fmt --all --check
+ cargo build
+ cargo build --features tray
+ cargo clippy --all-targets -- -D warnings
+ cargo clippy --all-targets --features tray -- -D warnings
+ cargo clippy --all-targets --features overlay -- -D warnings
+ cargo test
+ cargo test --features overlay
- # why: supply-chain gate (advisories / bans / sources / licenses) via cargo-deny.
- # deny.toml is already seeded and passes locally (`cargo deny check` is green). This lands
- # NON-BLOCKING (continue-on-error) only so the first CI run can't surprise-break a PR — flip it
- # to a required check (drop continue-on-error) after one green run on CI.
+ # Supply-chain gate (advisories / bans / sources / licenses) via cargo-deny, run against
+ # the GENERATED project (the raw template Cargo.toml has tokens and won't parse). REQUIRED:
+ # a deny.toml violation fails the build.
cargo-deny:
runs-on: ubuntu-latest
- continue-on-error: true
steps:
- - uses: actions/checkout@v4
- - uses: EmbarkStudios/cargo-deny-action@v2
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+ - uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
with:
- command: check advisories bans sources licenses
+ tool: cargo-generate,cargo-deny
+ - name: Generate a project from the template
+ run: cargo generate --path . --name deck-ci --silent --vcs none --destination "$RUNNER_TEMP/gen"
+ - name: cargo-deny check
+ working-directory: ${{ runner.temp }}/gen/deck-ci
+ run: cargo deny check advisories bans sources licenses
diff --git a/AGENTS.md b/AGENTS.md
index 8b1e454..5cad3ef 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -26,8 +26,25 @@ GPUI stack is pinned in `Cargo.lock`; the toolchain is pinned in
- `theme.rs` — dark/light palette with a selectable brand accent.
- `tray.rs` — optional menu-bar tray icon + dock hiding (`--features tray`).
+## This repo is a cargo-generate template — don't break it
+
+Deck ships via `cargo generate gh:hellno/deck`. The files in `cargo-generate.toml`'s
+`include` list (`Cargo.toml`, `Cargo.lock`, `src/main.rs`, `src/settings.rs`,
+`src/overlay/mod.rs`, `scripts/make-app-icon.py`, `LICENSE`) hold `{{ }}` Liquid tokens,
+so **the raw repo does not `cargo run` / `cargo build`** — render it first.
+
+- Verify template changes with **`just check-template`** (renders a project, then clippy +
+ test). `just run` / `just ci` only work *inside a generated fork*.
+- Renaming a new literal? Add its file to `include` **and** add the `{{ }}` token — a literal
+ in a non-included file ships as "deck" to every fork.
+- **Never** put a literal `{{` or `{%` in an included file (Liquid will try to parse it).
+- CI renders a throwaway project and builds THAT (`.github/workflows/ci.yml`).
+
## The loop (verify and self-correct without a CI round-trip)
+> In this template repo, the commands below run inside a **generated** project (or via
+> `just check-template`); the raw repo doesn't build. See the template note above.
+
- Type-check fast with `cargo check`. The full app build pulls GPUI from git, so
the **first** build is slow; it is cached after that. Run with `just run`
(`just run-tray` for the menu-bar variant).
@@ -49,8 +66,9 @@ All four must hold before you call a change done — `just ci` checks them at on
**Paste the command output as evidence; never claim done while anything is red.**
1. `cargo fmt --all --check` is clean.
-2. clippy `-D warnings` is green on **both** the default build **and**
- `--features tray` (`just check`).
+2. clippy `-D warnings` is green on **all** feature permutations — the default
+ build, `--features tray`, `--features overlay`, and `--features tray,overlay`
+ (`just check` runs all four).
3. `cargo test` is green (the `command_palette` fuzzy-match tests live here).
4. No new or changed deps in `Cargo.toml` / `Cargo.lock` unless explicitly
approved. The git GPUI stack is bumped **only** via `just bump-gpui` —
diff --git a/CLAUDE.md b/CLAUDE.md
index af16e72..ab31749 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -26,8 +26,25 @@ GPUI stack is pinned in `Cargo.lock`; the toolchain is pinned in
- `theme.rs` — dark/light palette with a selectable brand accent.
- `tray.rs` — optional menu-bar tray icon + dock hiding (`--features tray`).
+## This repo is a cargo-generate template — don't break it
+
+Deck ships via `cargo generate gh:hellno/deck`. The files in `cargo-generate.toml`'s
+`include` list (`Cargo.toml`, `Cargo.lock`, `src/main.rs`, `src/settings.rs`,
+`src/overlay/mod.rs`, `scripts/make-app-icon.py`, `LICENSE`) hold `{{ }}` Liquid tokens,
+so **the raw repo does not `cargo run` / `cargo build`** — render it first.
+
+- Verify template changes with **`just check-template`** (renders a project, then clippy +
+ test). `just run` / `just ci` only work *inside a generated fork*.
+- Renaming a new literal? Add its file to `include` **and** add the `{{ }}` token — a literal
+ in a non-included file ships as "deck" to every fork.
+- **Never** put a literal `{{` or `{%` in an included file (Liquid will try to parse it).
+- CI renders a throwaway project and builds THAT (`.github/workflows/ci.yml`).
+
## The loop (verify and self-correct without a CI round-trip)
+> In this template repo, the commands below run inside a **generated** project (or via
+> `just check-template`); the raw repo doesn't build. See the template note above.
+
- Type-check fast with `cargo check`. The full app build pulls GPUI from git, so
the **first** build is slow; it is cached after that. Run with `just run`
(`just run-tray` for the menu-bar variant).
@@ -49,8 +66,9 @@ All four must hold before you call a change done — `just ci` checks them at on
**Paste the command output as evidence; never claim done while anything is red.**
1. `cargo fmt --all --check` is clean.
-2. clippy `-D warnings` is green on **both** the default build **and**
- `--features tray` (`just check`).
+2. clippy `-D warnings` is green on **all** feature permutations — the default
+ build, `--features tray`, `--features overlay`, and `--features tray,overlay`
+ (`just check` runs all four).
3. `cargo test` is green (the `command_palette` fuzzy-match tests live here).
4. No new or changed deps in `Cargo.toml` / `Cargo.lock` unless explicitly
approved. The git GPUI stack is bumped **only** via `just bump-gpui` —
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..8db706c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,59 @@
+# Contributor Covenant Code of Conduct
+
+## Our pledge
+
+We as members, contributors, and leaders pledge to make participation in the
+Deck community a harassment-free experience for everyone, regardless of age,
+body size, visible or invisible disability, ethnicity, sex characteristics,
+gender identity and expression, level of experience, education, socio-economic
+status, nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our standards
+
+Examples of behavior that contributes to a positive environment:
+
+- Demonstrating empathy and kindness toward other people.
+- Being respectful of differing opinions, viewpoints, and experiences.
+- Giving and gracefully accepting constructive feedback.
+- Accepting responsibility, apologizing to those affected by our mistakes, and
+ learning from the experience.
+- Focusing on what is best for the overall community.
+
+Unacceptable behavior includes harassment, discrimination, personal or political
+attacks, publishing others' private information without permission, and any other
+conduct identified as unacceptable in the Contributor Covenant. Community leaders
+will remove, edit, or reject contributions that are not aligned with this Code of
+Conduct.
+
+## Enforcement responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards and
+will take appropriate and fair corrective action in response to any behavior they
+deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all community spaces (issues, pull requests,
+discussions) and also when an individual is officially representing the community
+in public spaces.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported privately to the maintainer (**@hellno**) through their
+[GitHub profile](https://github.com/hellno). All complaints will be reviewed and
+investigated promptly and fairly. Community leaders are obligated to respect the
+privacy and security of the reporter of any incident.
+
+> Maintainer: replace this with a dedicated contact email if you prefer one.
+
+## Attribution
+
+This Code of Conduct is adapted from the
+[Contributor Covenant](https://www.contributor-covenant.org), version 2.1,
+available at
+.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..1017780
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,90 @@
+# Contributing to Deck
+
+Deck is a small, opinionated **starter** (`0BSD`) — fork it, rename it, ship it.
+There's no domain logic to preserve, so the bar for upstream changes is "does it
+make the starter a better foundation for everyone who forks it?"
+
+You don't need to read this to *use* Deck — fork and go. This is for changes you
+want to land back **upstream**.
+
+## What's in scope
+
+Good upstream contributions:
+
+- **Linux fixes & polish** — the author daily-drives macOS; Linux is kept honest
+ by CI but isn't daily-driven. X11/Wayland bugs, packaging, and the tray GTK
+ loop are especially welcome.
+- **Cross-platform correctness** — anything that keeps the same code working on
+ both macOS and Linux.
+- **Docs** — clarifications to `README.md`, `docs/LEARNINGS.md`, or
+ `docs/UPGRADING.md`; hard-won GPUI gotchas are valuable.
+- **Small, focused features** that stay true to "kept small" — the starter is
+ ~700 lines on purpose.
+
+Probably out of scope: large frameworks, your app's domain logic, or anything
+that bloats the starter. When in doubt, open an issue first.
+
+## The rules (single-sourced in `CLAUDE.md` / `AGENTS.md`)
+
+The contributor rules and the agent rules are the same rules. The authoritative
+copy — including the full **Definition of Done**, the code constraints, and the
+performance rules — lives in **[`CLAUDE.md`](CLAUDE.md)** (mirrored verbatim in
+[`AGENTS.md`](AGENTS.md)). The *why* behind every lint and gate is in
+[`docs/AGENTIC-ENGINEERING.md`](docs/AGENTIC-ENGINEERING.md). Read those before a
+non-trivial change; the essentials are restated below.
+
+## The loop
+
+Install [`just`](https://github.com/casey/just) (`brew install just`), then:
+
+```bash
+just run # run the app (default features)
+just fix # auto-apply clippy's machine-fixable suggestions + format
+just ci # the FULL Definition of Done in one command (run before you push)
+```
+
+`just ci` mirrors `.github/workflows/ci.yml`, so **green locally == green in
+CI.** Loop `just fix` → `just ci` to self-correct without a CI round-trip.
+
+## Definition of Done
+
+A change is done only when all of these hold — `just ci` checks them at once.
+**Don't open a PR while anything is red.**
+
+1. `cargo fmt --all --check` is clean.
+2. `clippy -D warnings` is green on **every** feature config: default, `tray`,
+ `overlay`, and `tray,overlay` (`just check`).
+3. `cargo test` is green — including `cargo test --features overlay` (the
+ `command_palette` fuzzy-match tests and the overlay reducers live here).
+4. **No new or changed dependencies** in `Cargo.toml` / `Cargo.lock` without
+ explicit maintainer approval — open an issue to discuss first. A new store or
+ network client is a new dep and is approval-gated.
+
+### Bumping GPUI
+
+The `gpui` / `gpui_platform` / `gpui-component` / `gpui-component-assets` stack
+is pinned to exact commits in `Cargo.lock`. **Never hand-edit those pins.** Bump
+them only with:
+
+```bash
+just bump-gpui # cargo update on the four crates + rebuild
+```
+
+then commit the updated `Cargo.lock` (and `rust-toolchain.toml` if the build
+needed a newer toolchain). Full procedure and the crates.io fallback channel:
+[`docs/UPGRADING.md`](docs/UPGRADING.md).
+
+## Submitting a PR
+
+1. Branch off `main`.
+2. Make the change; run `just ci` until green.
+3. Open a PR and fill in the template — paste the `just ci` output as evidence
+ and confirm the no-new-deps box.
+
+Contributions are accepted under the project's [`0BSD`](LICENSE) license.
+
+## Reporting bugs & security
+
+- **Bugs / features:** use the issue templates (Bug report / Feature request).
+- **Security vulnerabilities:** do **not** open a public issue — see
+ [`SECURITY.md`](SECURITY.md).
diff --git a/Cargo.lock b/Cargo.lock
index 10ba808..58415f1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1386,7 +1386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
-name = "deck"
+name = "{{project-name}}"
version = "0.1.0"
dependencies = [
"directories",
diff --git a/Cargo.toml b/Cargo.toml
index a7e7756..b6f47aa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,13 +1,13 @@
[package]
-name = "deck"
+name = "{{project-name}}"
version = "0.1.0"
edition = "2021"
description = "An omakase native desktop app starter (macOS + Linux) on GPUI + gpui-component. Fork it, rename it, ship it."
license = "0BSD"
-default-run = "deck"
+default-run = "{{project-name}}"
[[bin]]
-name = "deck"
+name = "{{project-name}}"
path = "src/main.rs"
# Lint policy lives in the manifest (not just CI flags) so rust-analyzer and `cargo check` surface
@@ -89,10 +89,15 @@ codegen-units = 1
# (bakes the macOS squircle + builds icon.icns), then re-bundle.
# ---------------------------------------------------------------------------
[package.metadata.bundle]
-name = "Deck"
-identifier = "com.example.deck"
+name = "{{project-name | title_case}}"
+identifier = "{{bundle_qualifier}}.{{bundle_org}}.{{project-name}}"
icon = ["assets/icon.icns"]
category = "public.app-category.productivity"
short_description = "A small, snappy native desktop app starter built on GPUI."
-long_description = "Deck is a tiny, opinionated GPUI starter you can fork to build your own fast, native desktop app on macOS and Linux (and wire your own AI agent into it)."
+long_description = "{{project-name | title_case}} — a native desktop app built on GPUI + gpui-component for macOS and Linux."
osx_minimum_system_version = "11.0"
+# Ship the notices alongside the binary: Apache-2.0 §4 and ISC require their
+# notices to travel with redistributed binaries, so cargo-bundle copies these
+# into the .app / .deb.
+resources = ["NOTICE", "LICENSE"]
+copyright = "Copyright (c) 2026 {{project-name | title_case}} contributors. 0BSD. Bundles Apache-2.0/ISC/GPL-3.0-or-later third-party code; see NOTICE."
diff --git a/LICENSE b/LICENSE
index 8152524..20731cf 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
BSD Zero Clause License
-Copyright (c) 2026 Deck contributors
+Copyright (c) 2026 {{project-name | title_case}} contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
diff --git a/NOTICE b/NOTICE
index da6b7e9..63e56bc 100644
--- a/NOTICE
+++ b/NOTICE
@@ -22,9 +22,26 @@ use. Keep this notice if you redistribute the bundled icons. https://lucide.dev
GPUI and gpui-component
-----------------------
-- gpui (`gpui` 0.2.x on crates.io) — derived from Zed's GPUI. Apache-2.0 / GPL
- (see the upstream Zed repository for details).
-- gpui-component (`gpui-component` 0.5.x) — Apache-2.0, © Longbridge.
+The framework crates are pulled from git (pinned in Cargo.lock), not crates.io,
+and are all Apache-2.0:
+
+- gpui, gpui_platform, sum_tree — from Zed (https://github.com/zed-industries/zed,
+ pinned at commit 86effffd). Apache-2.0, © Zed Industries.
+- gpui-component, gpui-component-assets — from
+ https://github.com/longbridge/gpui-component (pinned at commit dadfca97).
+ Apache-2.0, © Longbridge.
+
+GPL-3.0-or-later obligation (please read before distributing a binary)
+......................................................................
+Zed's logging/tracing crates — zlog, ztracing, ztracing_macro — are
+GPL-3.0-or-later, and the git gpui stack links them statically into the binary
+via the chain `gpui -> sum_tree -> ztracing -> zlog`. Deck did not add them;
+they are inherent to building on Zed's git gpui stack. Because this code is
+compiled into a distributed Deck binary, that binary carries a GPL-3.0-or-later
+copyleft (offer-of-source) obligation: if you ship a binary, you must make the
+corresponding source available under GPL-3.0-or-later. This applies only to the
+binary — Deck's own source remains 0BSD. See deny.toml for the scoped license
+exceptions covering exactly these three crates.
tray-icon (optional, `--features tray`)
---------------------------------------
diff --git a/README.md b/README.md
index f31696b..82e60ab 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,9 @@
**A native desktop-app starter for macOS + Linux, built on [GPUI](https://www.gpui.rs/) + [gpui-component](https://github.com/longbridge/gpui-component).**
+[](https://github.com/hellno/deck/actions/workflows/ci.yml)
+[](LICENSE)
+
Fork it, rename it, ship it. You get a real title bar, the system menu bar, keyboard shortcuts,
a dark/light theme with a live accent picker, persisted settings, and an optional menu-bar (tray)
mode. Then delete the welcome screen and build your app — or wire in your own AI agent.
@@ -25,14 +28,30 @@ done once, opinionated, and kept small: ~700 lines across a few files, fresh git
## Quick start
+Deck is a **template** — scaffold your own renamed app with [`cargo-generate`](https://github.com/cargo-generate/cargo-generate):
+
```
-git clone my-app && cd my-app
+cargo install cargo-generate
+cargo generate gh:hellno/deck --name my-app
+```
+
+It renames the crate, app name, bundle id, and config-dir consts for you (answer the bundle-org
+prompt, or pass `-d bundle_org=acme`). Then:
+
+```
+cd my-app
cargo run
```
-The first build compiles GPUI from Zed's git from source (a few minutes, once); after that, rebuilds are
-fast. You need **`rustup`** — the exact Rust version is pinned in `rust-toolchain.toml` (currently
-`1.95.0`, matching Zed) and installed automatically on first build. Plus:
+> Cloning this repo and running `cargo run` directly won't work — its source carries `{{ }}`
+> template tokens (that's how the rename happens). Always scaffold via `cargo generate`.
+
+The first build compiles GPUI + wgpu from source and takes roughly 5–15 minutes depending on the
+machine (once); after that, rebuilds are fast. You need **`rustup`** — the exact Rust version is pinned
+in `rust-toolchain.toml` (currently `1.95.0`, matching Zed) and installed automatically on first build.
+[`just`](https://github.com/casey/just) is recommended for the task recipes (`cargo install just`, or
+`brew install just` / your distro's package) — every recipe is plain `cargo` underneath, so it's
+optional. Plus:
- **macOS 11+** — **Xcode Command Line Tools** (`xcode-select --install`). Apple Silicon + Intel.
Renders with **Metal**.
@@ -58,6 +77,7 @@ fast. You need **`rustup`** — the exact Rust version is pinned in `rust-toolch
| ⌨️ | **Keyboard shortcuts** → actions → menu items | `main.rs`, `shell.rs` |
| 📋 | Native **menu bar** (App / File / Edit / View) | `main.rs` |
| 🟣 | Optional **menu-bar / tray mode**, no dock icon — `--features tray` | `tray.rs` |
+| 🫧 | Optional **floating overlay** — transparent always-on-top window — `--features overlay` | `overlay/` |
| 🔣 | **Lucide** icon set (ISC licensed, bundled) | `gpui-component` |
| 🖼️ | **App icon** pipeline (image → squircle → icns) + `cargo bundle` config | `scripts/`, `assets/`, `Cargo.toml` |
@@ -120,6 +140,19 @@ it recolors to match your accent. Menu clicks are bridged back into GPUI on its
`tray-icon` is cross-platform (`NSStatusItem` on macOS, `libappindicator` on Linux); the dock-hiding
is macOS-only and cfg-gated. Architecture in [LEARNINGS §8](docs/LEARNINGS.md#tray).
+## Floating overlay (`--features overlay`)
+
+```
+cargo run --features overlay
+```
+
+This adds a transparent, always-on-top surface that floats over other apps — the seam for a HUD,
+a quick-capture bar, or an ambient agent panel. It's a real GPUI window (no second renderer); the
+panel is hardened via the shared `objc2` stack so it sits above full-screen spaces. macOS-only in
+v1; on Linux it compiles to a no-op (no LayerShell yet), so a Linux build still passes. The module
+lives in `src/overlay/` — see [docs/overlay.md](docs/overlay.md) for the design and the hardening
+details.
+
## Platforms
| | macOS | Linux |
@@ -139,7 +172,7 @@ on every push) but isn't daily-driven yet. Linux issues/PRs welcome.
2. **Change the display name** — `APP_NAME` in `src/main.rs` (drives the menu bar + window title).
3. **Change the bundle id** — `[package.metadata.bundle].identifier` in `Cargo.toml`, and the
`QUALIFIER/ORGANIZATION/APPLICATION` consts in `src/settings.rs` (they pick the config-dir path).
-4. **Swap the icon** — drop a 1024×1024 image at `assets/icon-source.png` and run `just icon` (bakes the macOS squircle tile + shadow and rebuilds `assets/icon.icns`), then `just bundle`.
+4. **Swap the icon** — drop a 1024×1024 image at `assets/icon-source.png` and run `just icon` (bakes the macOS squircle tile + shadow and rebuilds `assets/icon.icns`), then `just bundle`. `just icon` needs **Python 3 + Pillow** (`pip install pillow`); the `.icns` step is **macOS-only** (it shells out to `iconutil`), while the PNG it starts from is cross-platform.
5. **Replace the UI** — gut `src/welcome.rs` (and add routes in `src/shell.rs`) and build your thing.
Then ship a real app:
@@ -195,10 +228,14 @@ deck/
│ ├── settings.rs the persisted Settings struct (serde + config dir)
│ ├── settings_view.rs the settings page UI
│ ├── theme.rs refined palette + accent colors
-│ └── tray.rs optional menu-bar tray icon (feature = "tray")
+│ ├── tray.rs optional menu-bar tray icon (feature = "tray")
+│ └── overlay/ optional transparent always-on-top surface (feature = "overlay")
└── docs/
- ├── LEARNINGS.md deep dive: theme, icons, storage, menu bar, dock, tray
- └── UPGRADING.md how to bump gpui / gpui-component to the latest stable safely
+ ├── LEARNINGS.md deep dive: theme, icons, storage, menu bar, dock, tray
+ ├── UPGRADING.md how to bump gpui / gpui-component to the latest stable safely
+ ├── overlay.md the floating overlay surface: design + macOS hardening
+ ├── background-jobs.md running async work off the UI thread
+ └── AGENTIC-ENGINEERING.md why each lint + gate exists (the rules behind CLAUDE.md)
```
## Tech stack & the dependency story
@@ -232,3 +269,8 @@ Built on [Zed](https://github.com/zed-industries/zed) (GPUI) and
[Longbridge](https://github.com/longbridge/gpui-component) (gpui-component); icons are
[Lucide](https://lucide.dev) (ISC). Deck itself is 0BSD — zero-attribution, do whatever you want.
See [NOTICE](NOTICE) for third-party attributions.
+
+Note: distributed Deck binaries statically link GPL-3.0-or-later components (`zlog` / `ztracing` /
+`ztracing_macro`, pulled in transitively via `gpui`), so shipping a **binary** carries a GPL-3.0
+source-offer obligation; Deck's
+own source stays 0BSD. See [NOTICE](NOTICE) for details.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..cc62d7a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,37 @@
+# Security Policy
+
+Deck is a desktop-app **starter** — meant to be forked, renamed, and shipped as
+your own app. This policy covers the upstream starter repo
+(`github.com/hellno/deck`). **Your fork owns its own security posture:** once you
+add a model client, network calls, a data store, or any other domain logic, the
+threat model is yours, not the starter's.
+
+## Supported versions
+
+There are no release branches. The starter is shipped from `main`, and only the
+**latest `main`** receives security fixes. Pull the latest commit before
+reporting.
+
+## Reporting a vulnerability
+
+**Please report privately — do not open a public issue for a security bug.**
+
+Preferred: use GitHub's **Private Vulnerability Reporting** for this repo
+(Security tab → *Report a vulnerability*). It gives us a private thread and a
+coordinated-disclosure workflow.
+
+> Maintainer: enable Private Vulnerability Reporting under
+> *Settings → Code security and analysis* so the link above works.
+
+If that is unavailable, reach the maintainer (**@hellno**) through their
+[GitHub profile](https://github.com/hellno) and ask for a private channel before
+sharing any details.
+
+Please include:
+
+- the affected commit (the `gpui`/`deck` commit you're on),
+- OS and feature flags in play (`default` / `tray` / `overlay`),
+- a description of the issue and, ideally, a minimal reproduction.
+
+This is a single-maintainer project, so there is no formal SLA — expect a
+best-effort acknowledgement and a fix or mitigation on `main` once confirmed.
diff --git a/cargo-generate.toml b/cargo-generate.toml
new file mode 100644
index 0000000..53308d2
--- /dev/null
+++ b/cargo-generate.toml
@@ -0,0 +1,36 @@
+# cargo-generate template config for Deck.
+#
+# Deck is a cargo-generate TEMPLATE. Forkers run:
+# cargo install cargo-generate
+# cargo generate gh:hellno/deck --name my-app # answer the bundle-org prompt
+#
+# The files in `include` below contain {{ }} Liquid tokens, so the raw repo does
+# NOT build directly — CI generates a project first, then builds it. See
+# .github/workflows/ci.yml and the "cargo-generate template" note in CLAUDE.md.
+
+[template]
+cargo_generate_version = ">=0.18.0"
+
+# ALLOWLIST: ONLY these files are processed for Liquid `{{ }}` tokens. Every other
+# file (justfile, README, .github/*, docs/*, the other src/*.rs) is copied verbatim,
+# so their braces/`${{ }}`/code are never touched.
+#
+# ⚠️ Maintaining this: to rename a new literal, add its file here AND add the token.
+# NEVER put a literal `{{` or `{%` in an included file — Liquid would try to parse it.
+include = [
+ "Cargo.toml",
+ # Cargo.lock's root [[package]] name must track Cargo.toml's, or `cargo --locked`
+ # (in the generated fork's `just ci`) fails on the renamed root package.
+ "Cargo.lock",
+ "src/main.rs",
+ "src/settings.rs",
+ "src/overlay/mod.rs",
+ "scripts/make-app-icon.py",
+ "LICENSE",
+]
+
+[placeholders]
+# Reverse-DNS bundle identifier parts. `{{project-name}}` (from --name) is the third
+# segment, so the full id is `{{bundle_qualifier}}.{{bundle_org}}.{{project-name}}`.
+bundle_qualifier = { type = "string", prompt = "Bundle ID qualifier (reverse-DNS top segment)", default = "com", regex = "^[a-z][a-z0-9]*$" }
+bundle_org = { type = "string", prompt = "Bundle ID organization (reverse-DNS, e.g. your name or company)", default = "example", regex = "^[a-z0-9][a-z0-9-]*$" }
diff --git a/justfile b/justfile
index 6f37eac..7d42cef 100644
--- a/justfile
+++ b/justfile
@@ -43,6 +43,14 @@ ci:
cargo test --locked
cargo test --locked --features overlay
+# Verify the TEMPLATE itself: this repo's source carries {{ }} tokens and does NOT build
+# directly, so render a throwaway project (outside the repo, like CI) and lint + test that.
+# Needs cargo-generate (`cargo install cargo-generate`). Inside a generated fork, use `just ci`.
+check-template:
+ rm -rf "${TMPDIR:-/tmp}/deck-template-check"
+ cargo generate --path "{{justfile_directory()}}" --name app --silent --vcs none --destination "${TMPDIR:-/tmp}/deck-template-check"
+ cd "${TMPDIR:-/tmp}/deck-template-check/app" && cargo clippy --all-targets --features tray,overlay -- -D warnings && cargo test && cargo test --features overlay
+
# Auto-fix everything fixable: clippy's machine-applicable suggestions + formatting.
# Re-run `just ci` afterwards to confirm green. (--allow-dirty so it works mid-edit.)
fix:
diff --git a/scripts/make-app-icon.py b/scripts/make-app-icon.py
index 91054ca..523479b 100644
--- a/scripts/make-app-icon.py
+++ b/scripts/make-app-icon.py
@@ -126,7 +126,7 @@ def write_linux(fullbleed, name):
made.append(p)
desktop = os.path.join(root, f"{name}.desktop")
with open(desktop, "w") as f:
- f.write("[Desktop Entry]\nType=Application\nName=Deck\n"
+ f.write("[Desktop Entry]\nType=Application\nName={{project-name | title_case}}\n"
"Comment=Native cross-platform desktop app starter built on GPUI\n"
f"Exec={name}\nIcon={name}\nTerminal=false\n"
"Categories=Development;Utility;\n")
@@ -136,7 +136,7 @@ def write_linux(fullbleed, name):
def main():
ap = argparse.ArgumentParser(description="Generate macOS/Linux/web app icons from one image.")
ap.add_argument("source", nargs="?", default=os.path.join(ROOT, "assets", "icon-source.png"))
- ap.add_argument("--name", default="deck")
+ ap.add_argument("--name", default="{{project-name}}")
ap.add_argument("--out", default=os.path.join(ROOT, "assets"))
ap.add_argument("--no-mask", action="store_true")
ap.add_argument("--no-shadow", action="store_true")
diff --git a/src/main.rs b/src/main.rs
index f0e4b2c..0b9bfb4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,12 +28,12 @@ use settings::Settings;
use shell::Shell;
/// The display name used in the menu bar and window. Change this first when forking.
-pub const APP_NAME: &str = "Deck";
+pub const APP_NAME: &str = "{{project-name | title_case}}";
// Declare the app's actions. Each becomes a zero-sized struct you can bind a key
// to, hang a menu item off of, and handle in a view or globally. Add your own here.
gpui::actions!(
- deck,
+ {{crate_name}},
[
Quit,
About,
diff --git a/src/overlay/mod.rs b/src/overlay/mod.rs
index df47a6f..bdf9d22 100644
--- a/src/overlay/mod.rs
+++ b/src/overlay/mod.rs
@@ -29,7 +29,7 @@ use state::OverlayAnchor;
// The recording toggle action. Declared cross-platform (acceptance criterion #8: the
// action + its keybinding must compile on Linux); the *handler* only does work on
// macOS, where the pill window exists.
-gpui::actions!(deck, [ToggleRecording]);
+gpui::actions!({{crate_name}}, [ToggleRecording]);
// Globals holding a weak handle to each live overlay view (for background pushes +
// the recording toggle) plus the window's root handle (held for lifecycle/teardown).
@@ -109,7 +109,7 @@ fn env_override_bool(key: &str) -> Option {
let raw = std::env::var(key).ok()?;
let parsed = parse_bool_override(&raw);
if parsed.is_none() {
- eprintln!("deck: ignoring {key}={raw:?} (expected 1/0/true/false)");
+ eprintln!("{{project-name}}: ignoring {key}={raw:?} (expected 1/0/true/false)");
}
parsed
}
diff --git a/src/settings.rs b/src/settings.rs
index 8d84990..fefb45d 100644
--- a/src/settings.rs
+++ b/src/settings.rs
@@ -16,9 +16,9 @@ use crate::theme::Accent;
// Reverse-DNS used for the config dir. Keep in sync with the bundle identifier
// in Cargo.toml when you fork. (qualifier, organization, application)
-const QUALIFIER: &str = "com";
-const ORGANIZATION: &str = "Example";
-const APPLICATION: &str = "Deck";
+const QUALIFIER: &str = "{{bundle_qualifier}}";
+const ORGANIZATION: &str = "{{bundle_org}}";
+const APPLICATION: &str = "{{project-name}}";
/// Persisted theme preference. We keep our own enum (rather than reusing
/// gpui-component's `ThemeMode`) so the on-disk format is ours to control.
@@ -114,7 +114,7 @@ impl Settings {
/// handling) when the write is load-bearing — e.g. an agent fork's chat history.
pub fn save_best_effort(&self) {
if let Err(err) = self.save() {
- eprintln!("deck: could not save settings: {err}");
+ eprintln!("{{project-name}}: could not save settings: {err}");
}
}
}