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).** +[![CI](https://github.com/hellno/deck/actions/workflows/ci.yml/badge.svg)](https://github.com/hellno/deck/actions/workflows/ci.yml) +[![License: 0BSD](https://img.shields.io/badge/license-0BSD-blue.svg)](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}"); } } }