Guidance for coding agents (Codex and others) working in Deck. Claude Code reads
the identical rules in CLAUDE.md — the two files are kept in lockstep. The
why behind every lint and gate lives in docs/AGENTIC-ENGINEERING.md.
Deck is a native desktop-app starter built on GPUI + gpui-component in Rust (macOS + Linux, rendered on Metal/wgpu). It is meant to be forked, renamed, and shipped as your own app — there is no domain logic to preserve, just a clean, working foundation.
It is a single crate (package deck, license 0BSD, edition 2021). The git
GPUI stack is pinned in Cargo.lock; the toolchain is pinned in
rust-toolchain.toml to match Zed's gpui build — do not change either casually.
main.rs— entry point; opens the window and wires up the app.shell.rs— the single root view; owns persistedSettingsand routing.command_palette.rs— the ⌘K (Ctrl K) launcher, with fuzzy-match tests.welcome.rs— the Welcome page (a centered card).settings.rs— typed preferences persisted to the platform config dir.settings_view.rs— the Settings page rendered byShell.theme.rs— dark/light palette with a selectable brand accent.tray.rs— optional menu-bar tray icon + dock hiding (--features tray).
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 cionly work inside a generated fork. - Renaming a new literal? Add its file to
includeand 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).
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 withjust run(just run-trayfor the menu-bar variant). just ciruns the entire Definition of Done in one command (fmt-check + clippy on both feature configs + tests) — the same gate as CI, so green here means green there. Run it before declaring a change done.just fixauto-applies clippy's machine-fixable suggestions and formats. Loopjust fix→just cito self-correct.- For self-correction, prefer machine-readable diagnostics:
cargo clippy --message-format=short(one line each) or--message-format=json(structured spans + applicable fixes). The default human format is for people. - Editor == CI:
.vscode/and.zed/set rust-analyzer to check with clippy, so in-editor warnings match the CI lint set (rust-analyzer otherwise runs plaincargo checkand would miss the[lints.clippy]rules).
All four must hold before you call a change done — just ci checks them at once.
Paste the command output as evidence; never claim done while anything is red.
cargo fmt --all --checkis clean.- clippy
-D warningsis green on all feature permutations — the default build,--features tray,--features overlay, and--features tray,overlay(just checkruns all four). cargo testis green (thecommand_palettefuzzy-match tests live here).- No new or changed deps in
Cargo.toml/Cargo.lockunless explicitly approved. The git GPUI stack is bumped only viajust bump-gpui— never hand-edit those pins.
These mirror the manifest lints; internalize them before writing code:
- No
todo!()and nodbg!()— both are denied. - Never ignore a
Result—unused_must_useis denied (a dropped fallible IO/serde result is a silent bug). Propagate with?or handle it. unsafe_codeis denied crate-wide. A genuinely necessaryunsafeblock needs a reviewed// SAFETY:comment plus a scoped#[allow(unsafe_code)].- Deck is an app, so
.expect()on genuinely-infallible GPUI handles is fine (seemain.rsandtray.rs). PreferResult/?everywhere else.
Deck should feel instant for the same reason Linear does: nothing on the UI hot
path waits on I/O or the network. A native GPUI app already gets most of "how is
Linear so fast" for free — the heap is your data store, there's no bundle to split,
no reflow, no vdom diff — so these are the few rules that still need discipline.
Full contrast and rationale: docs/LEARNINGS.md §17.
- Never block the render thread on I/O. Apply changes to in-memory state and
cx.notify()now; persist off the hot path — at a coarse boundary (blur/commit) or oncx.background_executor(), never on a per-keystrokeInputEvent::Change. UseSettings::save_best_effort()for UI writes;save()returns theio::Resultwhen a write is load-bearing. cx.notify()the smallest entity that changed. It marks the view and its ancestors dirty, so volatile state held as fields onShellrepaints the whole page. Give it its ownEntity<T>(likename_inputand the palette), not the root.- Render large lists with
uniform_list/list, never a flex column of N children (it rebuilds N elements + N layout nodes every frame). - Filter and search in memory — the ⌘K palette already does (synchronous, no I/O per keystroke). Do the same for your own pickers.
- Prefer an in-memory
Entityover a data store — any new store or network client is a new dep, which is approval-gated (Definition of Done #4).
Ground UI changes in README.md and docs/LEARNINGS.md (real screenshots and
hard-won GPUI lessons) — not remembered descriptions of how things look.
Close the visual loop — just screenshot instead of guessing. After a UI edit,
capture the result and look at the image (this is the OODA loop: observe the real
pixels, don't assume). It launches the app, optionally drives it to a view, captures
the front window, and quits — also how you refresh the README shots. macOS only; a
human grants your terminal Screen Recording + Accessibility once (System
Settings → Privacy & Security), so a sandboxed session can't do the first run unaided.
just screenshot # welcome → docs/screenshot.png
just screenshot docs/screenshot-settings.png "" cmd+, # settings page
just screenshot docs/screenshot-palette.png "" cmd+k # command palette
just screenshot docs/overlay.png overlay # overlay rail + pill (alpha, leak-proof)
SHOT_BACKDROP=zed just screenshot docs/tray.png tray # menu-bar tray icon + its menu
Overlay/tray capture is --features-driven (see the recipe). Hover states still need a
cursor tool (e.g. cliclick) — out of scope.