# Gitwig Style Guide
## Rust Coding Standards
- **Naming:** Follow standard Rust naming conventions (`snake_case` for functions/variables, `PascalCase` for structs/enums).
- **Errors:** Use `thiserror` or `anyhow` for error handling (or standard `Box<dyn Error>` for top-level). Prefer specific error enums for library logic.
- **No Panics in Hot Paths:** Avoid `.unwrap()` or `.expect()` inside the main rendering or event loop. Handle failures gracefully by appending to an in-app error log or the transient status-bar notification.
- **No Bare Subprocesses:** Never call `std::process::Command::new` directly (clippy `disallowed-methods` fails the build). Build children with `gitwig_core::git_command` / `tool_command` / `detached_command`, which detach stdin, stdout and stderr from the terminal so a child can never print over the alternate screen or block on an invisible prompt. Only editors, shells, `git rebase -i` and `git mergetool` may own the tty, via `git_cmd::interactive_command`, and only after `LeaveAlternateScreen`. A child whose stdout is streamed must drain stderr too (`spawn_pipe_reader`) and surface anything useful as a status-bar notice, never as a failed tab.
- **Documentation:** Use doc comments (`///`) for public items. Explain *why*, not just *what*. Use a top-of-file `//!` doc comment to state each module's single responsibility.
- **Imports:** Group imports: std, external crates, internal modules. Use curly braces for multiple items from the same crate.
- **File Size:** New modules should stay under ~300 lines, and every new feature lands in its own module rather than growing one of the existing large files (`src/app/tests.rs`, `src/ui/draw.rs`, `src/app/navigation.rs`, `src/app/mod.rs`, …). Those are split opportunistically along a single responsibility line (state vs. rendering vs. input vs. persistence) when a change touches them. See the Standard Blueprint in §3 "Architecture & Refactoring Thresholds" of `.agent/INSTRUCTIONS.md` for the current map and the splitting rules.
## TUI Patterns
- **Immediacy:** The UI should feel snappy. Avoid unnecessary redraws, but ensure state changes are reflected immediately.
- **Keybindings:** Use standard TUI keybindings where appropriate (e.g., `q` for quit, `j`/`k` or arrows for navigation).
- **Layout:** Use `ratatui`'s `Layout` and `Constraint` system to handle terminal resizing gracefully.
- **Widgets:** Keep widgets stateless where possible. Pass the necessary state during the `render` call.
- **State vs. Widgets:** Keep long-lived data and component states (e.g., `ListState`, `TableState`) inside the core `App` or component structs. Widgets are short-lived, ephemeral view structures constructed on-the-fly during each rendering frame. State modifications must only occur within event-handling functions — never mutate state inside rendering functions.
- **Draw Cycle Efficiency:** Trust ratatui's double-buffered frame diffing. Avoid creating custom buffers or copying large datasets inside the rendering loop. Pass data to widgets via lightweight references (`&str` or cloned slices) rather than transferring ownership.
- **Theme:** Centralize color and border choices in `src/ui/style.rs`. They are theme-backed accessor *functions*, not consts — `ACCENT()`, `WARNING()`, `DANGER()`, `SUCCESS()`, `CARD_BORDER()` — because the active theme can change at runtime; call them at render time alongside the style helpers (`muted_style`, `primary_style`, `accent_style`). Use rounded borders (`BorderType::Rounded`) by default — they read as modern and avoid the heavy "box-in-box" effect of double or thick borders.
- **Background-agnostic text:** Never hard-code `Color::White`, `Color::Gray`, `Color::DarkGray`, or `Color::Black` for regular text or borders — pick whichever looks fine on your dev terminal and it will be unreadable on the opposite background. Leave the foreground at the terminal default and use `Modifier::DIM` for muted text and `Modifier::BOLD` for emphasis. Explicit foreground colors are reserved for accents (cyan / yellow / red) and badge foregrounds (where the badge has its own solid background).
- **Brand colors are not theme colors:** The logo mark is the one place that ignores the active theme. Use `BRAND_VERDIGRIS()` / `BRAND_COPPER()` from `src/ui/style.rs` — never a raw `Color::Rgb` literal — and only for the mark itself (About popup art, empty-state ring), so it renders in brand colors under every theme. Everything else follows `ACCENT`/`WARNING`/`DANGER`/`SUCCESS`. The palette's source of truth is `branding/theme.json`; `branding/README.md` lists every surface that consumes it.
- **Shared input buffer:** `App::input_buffer` carries a caret (`input_cursor`). Mutate it only through `input_char`, `input_str`, `input_backspace`, `input_delete`, and the `input_left`/`right`/`home`/`end` movers, and read the caret via `input_cursor_clamped()` — never the raw field. Seeding a field with existing text should use `set_input_buffer`, which parks the caret at the end. Direct assignment still works (the caret detects the length change and snaps to the end) but is the slower path and easy to get wrong. The caret counts **characters, not bytes**, so multi-byte input cannot split a `char` boundary.
- **Selection feedback:** Indicate the selected element with three reinforcing signals — a left-edge `▌` mark in the accent color, an accent-colored border, and bold primary text. Switch the border color (not the text color) to communicate pending destructive or text-edit actions.
- **Status bar:** Use a `MODE` badge with a colored background on the left and muted contextual text on the right, separated by ` ⟩ ` (or ` > ` in compatibility mode). Use the real terminal cursor (`Frame::set_cursor_position`) in input modes rather than a fake `_` character — it tracks blink and theme correctly.
- **Clickable regions:** Never re-derive a click zone in `mouse.rs` from guessed widths. Either record the rect during the draw pass (an out-param of `ui::draw` like `global_summary_area` / `quick_label_area`, or a `Cell<Option<Rect>>` on `App` like `label_badge_area` for something drawn outside the content area) or expose the exact caption strings the renderer uses (`summary_tab_parts`, `quick_label_parts`) and measure those. Clear a recorded rect on every frame that does not draw the element, so a stale rect cannot swallow clicks. For a scrollable table, also record the offset its `TableState` scrolled to (`DetailAreas::*_offset`, read after `render_stateful_widget`) and map clicks through it (`clicked_table_row` in `mouse.rs`): the row under the pointer is `offset + visible row`, not the visible row.
- **Header rules:** Separators inside the content area (the home header's rules) use the `rule` symbol (`┄`, `-` in compatibility mode — defined in the symbols table in `src/config.rs`, not in `src/keys/symbols.rs`) in `muted_style()`, so they read as secondary to the solid frame and card borders. Reserve solid `─` for borders.
- **The open repository vs. the home cursor:** `App::get_selected_item()` is the *home list's* cursor — an index into the grouped, sorted home rows, which reorder under you (opening a repository records a visit that lifts it to the top of the Recent group; sorts and label changes reshuffle rows). Inside a repository view, name, theme, settings and anything else meaning "this repository" must come from `App::active_repo_item()`, which is derived from `current_detail` (or `loading_repo_path` while loading) and only falls back to the cursor on the home screen. To open a specific repository, call `App::open_repo(item)`; never write a `config.items` or `get_filtered_items()` position into `selected_index`, which indexes home rows (they diverge whenever grouping is on). To keep the cursor on a repository across anything that reorders the rows (a sort, a pin or star, an edit), take `App::home_cursor()` (the item and the group its row is under) beforehand and hand both to `App::select_home_row(item, group)` afterwards; to land on a repository just added, use `App::reveal_home_row(item)`, which also expands its collapsed label group.
## Git Integration
- **Atomicity:** Ensure that any multi-step Git operation (like a merge or rebase) handles failures gracefully and doesn't leave the repo in a broken state if possible.
- **Performance:** For large lists (like a commit log), use lazy loading or pagination if necessary. `ratatui`'s `List` or `Table` can handle many items if used correctly.