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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/commands/plan-next.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Read CLAUDE.md, PROJECT_STATE.md, and PLAN.md only.
Do not scan or explore the src/ directory yet.
Summarize the next incomplete task from PLAN.md.
List what files you expect to touch and why.
Then wait for my confirmation before doing anything.
69 changes: 69 additions & 0 deletions .claude/skills/gtk4-rs-master.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
name: gtk4-rs-master
description: Patterns for GTK4 and Adwaita in Rust — UI state, signals, async, and the GTK4 list model.
---

# GTK4 Rust Specialist

## 1. Signal Handling & Closures

- **Use `glib::clone!`**: Always use the `clone!` macro when passing widgets or state into signal handlers (e.g., `button.connect_clicked(clone!(@weak label => move |_| ...))`).
- **Weak References**: Always prefer `@weak` references for widgets in closures to avoid reference cycles and memory leaks. Use `@strong` only when the closure must keep the object alive (rare).
- **`connect_closure!`**: For custom signals on subclassed GObjects, use `connect_closure!` when `connect_*` methods are not available. This is common when defining your own signals via `ObjectImpl`.
- **Signal Cleanup**: For long-lived connections that outlast a view, store `SignalHandlerId` and disconnect explicitly on dispose/teardown.

## 2. State Management

- **Interior Mutability**: Use `Rc<RefCell<T>>` for shared application state that needs to be modified from UI signals.
- **Properties**: For complex widgets, prefer `glib::Properties` and `glib::Object` subclassing over raw structs where appropriate.
- **Property Bindings**: When two widgets need to stay in sync, use `bind_property("source-prop", &target, "target-prop").sync_create().build()` instead of manual signal handlers. This is cleaner and handles lifecycle automatically.

## 3. UI Construction

- **GTK4 Defaults**: Use `gtk::Application` and `gtk::ApplicationWindow`. Do not use `gtk::main()` (GTK3 style).
- **Adwaita**: If the project uses `libadwaita`, prefer `adw::Application` and `adw::ApplicationWindow` for a modern GNOME look. Use `adw::NavigationView`, `adw::ToolbarView`, etc. for navigation patterns.
- **Composition**: Prefer `.ui` files (XML) with `gtk::Builder` or composite templates (`#[derive(CompositeTemplate)]`) for complex layouts. Use programmatic construction only for simple or dynamic UIs.

## 4. Layout & Widgets

- **No `add()`**: `gtk::Container` is gone in GTK4. Use `.set_child()` for single-child containers, `box_.append()` for `gtk::Box`, `grid.attach()` for `gtk::Grid`, etc.
- **String Handling**: Use `.to_string()` or `.as_str()` explicitly when passing Rust strings to GTK methods. GTK methods often expect `&str` or `Option<&str>`.
- **Sizing**: Prefer `set_hexpand(true)` / `set_vexpand(true)` and `set_halign()` / `set_valign()` over fixed sizes. Let the layout engine do its job.

## 5. List Model (GTK4 Pattern)

This is the biggest GTK3 → GTK4 change. Do NOT use `TreeView`/`ListStore` from GTK3.

- **Model**: Use `gio::ListStore` to hold your data objects (which must be `glib::Object` subclasses).
- **Selection**: Wrap in `gtk::SingleSelection` or `gtk::MultiSelection`.
- **View**: Use `gtk::ListView` (or `gtk::GridView`, `gtk::ColumnView`).
- **Factory**: Use `gtk::SignalListItemFactory` and connect `setup` + `bind` signals to create and populate row widgets.
- **Pattern**:
```rust
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(|_, list_item| { /* create widgets */ });
factory.connect_bind(|_, list_item| { /* bind data to widgets */ });
let selection = gtk::SingleSelection::new(Some(model));
let list_view = gtk::ListView::new(Some(selection), Some(factory));
```

## 6. Async in GTK

GTK is single-threaded. You cannot touch widgets from a background thread.

- **`glib::spawn_future_local()`**: Use this to run async code on the GLib main loop. This is the correct way to do async work that needs to update the UI.
- **Do NOT use `tokio::spawn`** for anything that touches widgets. Tokio tasks run on a thread pool and will panic or cause UB.
- **Background Work**: If you need real async I/O (network, disk), spawn it on tokio, then send the result back via a `glib::MainContext` channel or `spawn_future_local`.
- **Pattern**:
```rust
glib::spawn_future_local(clone!(@weak label => async move {
let result = gio::spawn_blocking(|| expensive_computation()).await.unwrap();
label.set_text(&result);
}));
```

## 7. Resources & Actions

- **GResource**: Compile UI files, icons, and CSS into a `.gresource` bundle via `glib_build_tools::compile_resources()` in `build.rs`.
- **CSS**: Load stylesheets via `gtk::CssProvider` and `gtk::style_context_add_provider_for_display()`.
- **Actions**: Use `gio::SimpleAction` for menu items and keyboard shortcuts. Attach to the `ApplicationWindow` or `Application` as appropriate.
49 changes: 49 additions & 0 deletions .claude/skills/rust-expert.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
name: rust-expert
description: Advanced Rust patterns, focusing on ownership, safety, and performance. Apply to all .rs files and Cargo.toml changes.
---

# Rust Expert Skill

## 1. Ownership & Lifetimes

- **Borrow Checker First:** Always prefer borrowing (`&T` or `&mut T`) over cloning (`.clone()`) unless the data must be owned.
- **Lifetime Elision:** Do not manually specify lifetimes (e.g., `<'a>`) unless the compiler cannot infer them.
- **Smart Pointers:** Use `Rc<T>` for multiple readers, `Arc<T>` for thread-safe sharing, and `Box<T>` for heap allocation of large structs.
- **`Cow<str>`:** When an API sometimes needs an owned `String` and sometimes a `&str`, use `Cow<'_, str>` to avoid unnecessary allocations. This is especially relevant at FFI/GTK boundaries.

## 2. Error Handling

- **Production Code:** Avoid `unwrap()` and `expect()` in production code paths. Use `?` for propagation.
- **Tests & Build Scripts:** `unwrap()` and `expect()` are acceptable in `#[test]` functions, `build.rs`, and examples where a panic is the correct failure mode.
- **After Validation:** `unwrap()` is acceptable immediately after an explicit check (e.g., `if option.is_some() { option.unwrap() }`), but prefer `if let` or `match` instead — it's safer and more idiomatic.
- **Result Types:** Prefer the `anyhow` crate for application logic and `thiserror` for library-grade error enums.
- **Context:** Always use `.context("...")` or `.with_context(|| format!(...))` with anyhow to provide a stack-trace-like experience.

## 3. Style & Idioms

- **Pattern Matching:** Use `match` or `if let` instead of nested `if` statements for `Option` and `Result`.
- **Clippy:** Assume `cargo clippy` is active. Write code that passes default linting rules.
- **Functional Style:** Use iterator chains (`.map()`, `.filter()`, `.collect()`) where it improves readability over `for` loops. Don't force chains when a `for` loop with early returns is clearer.
- **Type Inference:** Let the compiler infer types. Don't annotate variables unless it aids readability or resolves ambiguity (e.g., `.collect::<Vec<_>>()`).
- **`impl` Over Generics in Args:** Prefer `fn foo(s: impl AsRef<str>)` over `fn foo<S: AsRef<str>>(s: S)` for single-use bounds to reduce visual noise.

## 4. Module Organization

- **Visibility:** Default to private. Use `pub(crate)` for internal sharing, `pub` only for the public API.
- **Thin Entry Points:** Keep `main.rs` and `lib.rs` thin — they should primarily re-export and wire things together.
- **Grouping:** One module per logical concern. If a file exceeds ~300 lines, consider splitting into a `module/mod.rs` + sub-files structure.
- **Re-exports:** Use `pub use` in `lib.rs` or `mod.rs` to flatten the public API so consumers don't need deep paths.

## 5. Modern Tooling

- **Async:** Use `tokio` as the default runtime. Use `#[tokio::main]`.
- **Serialization:** Use `serde` with `#[derive(Serialize, Deserialize)]`.
- **Feature Flags:** Gate optional dependencies behind Cargo features. Don't compile what you don't use.
- **Workspace:** For multi-crate projects, use a Cargo workspace to share dependencies and build settings.

## 6. Performance Defaults

- **Allocations:** Be allocation-aware. Prefer `&[T]` over `Vec<T>` in function signatures when ownership isn't needed. Use `String` only when you must own it.
- **`#[inline]`:** Don't add `#[inline]` unless profiling shows it matters. Let the compiler decide.
- **Release Profile:** Ensure `Cargo.toml` has `[profile.release] lto = true` for final builds when binary size/speed matters.
77 changes: 77 additions & 0 deletions .claude/skills/rust-gtk-project-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
name: rust-gtk-project-conventions
description: Project structure, build conventions, and workflow rules for a Rust + GTK4/Adwaita application. Apply alongside rust-expert and gtk4-rs-master.
---

# Rust GTK Project Conventions

## 1. Project Structure

Follow this layout for a typical GTK4 Rust application:

```
project-root/
├── Cargo.toml
├── build.rs # GResource compilation
├── data/
│ ├── resources.gresource.xml
│ ├── icons/
│ ├── style.css
│ └── ui/ # .ui template files
│ ├── window.ui
│ └── preferences.ui
├── src/
│ ├── main.rs # Entry point — thin, just boots the app
│ ├── application.rs # Application subclass, activate/startup
│ ├── config.rs # Build-time constants (app ID, version)
│ ├── window/
│ │ ├── mod.rs # Window subclass + CompositeTemplate
│ │ └── imp.rs # ObjectImpl, WidgetImpl, etc.
│ ├── widgets/ # Custom reusable widgets
│ └── models/ # GObject model classes for ListStore
└── CLAUDE.md # This project's rules for Claude Code
```

- Keep `main.rs` under 30 lines. It should only create and run the `Application`.
- Each custom widget or GObject subclass gets its own directory with `mod.rs` + `imp.rs`.
- All `.ui` files live in `data/ui/`. Don't scatter them in `src/`.

## 2. Cargo.toml Conventions

- **Edition**: Always use `edition = "2021"` (or later if available).
- **Dependencies**: Pin GTK4/Adwaita crate versions to a specific minor version (e.g., `gtk = { version = "0.9", package = "gtk4" }`). GTK crate versions map to specific GTK C library versions — mixing them breaks builds.
- **Features**: Use feature flags for optional capabilities (e.g., `[features] libadwaita = ["dep:libadwaita"]`).
- **Release Profile**:
```toml
[profile.release]
lto = true
strip = true
codegen-units = 1
```

## 3. Build Process

- **Check before committing**: Always run `cargo clippy -- -W clippy::all` and `cargo fmt --check` before treating code as done.
- **Test**: Run `cargo test` to ensure nothing is broken. UI code is hard to unit test — focus tests on model/logic code.
- **GResource**: The `build.rs` should call `glib_build_tools::compile_resources()`. If the build fails with missing resources, check that `resources.gresource.xml` lists all files.

## 4. Naming Conventions

- **Application ID**: Use reverse-DNS (e.g., `com.github.username.appname`). Must match what's in `.desktop` and `resources.gresource.xml`.
- **Signal Names**: Use kebab-case (e.g., `"item-selected"`).
- **Property Names**: Use kebab-case (e.g., `"is-active"`). The `glib::Properties` macro converts `snake_case` Rust fields automatically.
- **CSS Classes**: Use kebab-case. Prefer Adwaita's built-in style classes (`.title-1`, `.card`, `.navigation-sidebar`) over custom CSS when possible.

## 5. Git Conventions

- **Commits**: Use conventional commits — `feat:`, `fix:`, `refactor:`, `chore:`, `docs:`.
- **Don't commit**: `target/`, `.flatpak-builder/`, `*.gresource` (compiled), `.env` files.
- **Do commit**: `.ui` files, `Cargo.lock` (it's an application, not a library), `CLAUDE.md`.

## 6. Common Pitfalls to Avoid

- **Don't use `gtk::main()`** — that's GTK3. Use `application.run()`.
- **Don't use `TreeView`** — use the new `ListView` + `ListStore` + `SignalListItemFactory` model.
- **Don't call widget methods from threads** — use `glib::spawn_future_local()` or `MainContext` channels.
- **Don't ignore deprecation warnings** — GTK4 moves fast and deprecated APIs get removed in the next major version.
- **Don't hard-code strings** — if the app will ever be translated, use `gettext` or `gettextrs` from the start.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/target
notes.txt
opencode.json
Loading
Loading