# Project Editing + Catppuccin Frappé Theme Completion **Date:** 2026-06-09 **Status:** Approved ## Problem 1. Projects cannot be edited after creation. If a repository moves on disk, the only options are archiving the project or editing the database by hand. 2. The TUI registers a Catppuccin Frappé theme (`FRAPPE_THEME` in `src/hqt/tui/app.py`) but only sets the basic semantic fields. Textual auto-derives every other color (footer keys, borders, selection highlights), which drifts off-palette, and the modal dialogs, panel headers, and status symbols are entirely unstyled. The result does not look like Frappé. ## Part 1: Project editing ### Service layer Add to `ProjectService` (`src/hqt/projects/service.py`): ```python def update(self, project_id: int, name: str, path: str) -> Project ``` - Updates both fields and commits. - Raises `ValueError("Project not found")` for an unknown id. - A duplicate path violates the unique constraint on `Project.path`: catch `IntegrityError`, roll back, raise `ValueError` with a readable message (e.g. `"Another project already uses path "`). - No filesystem validation (consistent with `create`, which accepts any string). ### Form screen Rename `AddProjectScreen` (`src/hqt/tui/screens/add_project.py`) to `ProjectFormScreen`: - Constructor: `ProjectFormScreen(title="Add Project", initial_name="", initial_path="")`. - The dialog heading label shows `title`; inputs are pre-filled with the initial values. - Return type unchanged: `tuple[str, str] | None` of `(name, path)`. - Add-mode behavior unchanged: empty name defaults to `Path(path).name`; empty path dismisses with `None`. ### Project list Add `ProjectList.get_selected_project_id() -> int | None` (`src/hqt/tui/widgets/project_list.py`), same pattern as `SessionList.get_selected_session_id()`: read `data` off the ListView's highlighted child. ### App wiring In `HqtApp` (`src/hqt/tui/app.py`): - New binding: `Binding("e", "edit_project", "Edit Project")`. - `action_edit_project`: - No project highlighted → `notify("Select a project first", severity="warning")` and return. - Otherwise push `ProjectFormScreen(title="Edit Project", initial_name=project.name, initial_path=project.path)`. - On dismiss with a result: call `ProjectService.update`, refresh the project list. Catch `ValueError` and show its message as an error notification. - `action_add_project` switches to the renamed `ProjectFormScreen` with defaults; behavior otherwise unchanged. ### Session semantics Editing a path never touches tmux. Running sessions keep the working directory they were spawned with; only sessions created after the edit use the new path. This matches archive semantics (DB-only operation). ## Part 2: Frappé theme completion ### Theme definition Replace `FRAPPE_THEME` with a fully specified `Theme`, mirroring the structure of Textual's built-in `catppuccin-mocha` theme with Frappé values: | Field | Frappé color | Hex | |--------------|--------------|-----------| | `primary` | Blue | `#8caaee` | | `secondary` | Mauve | `#ca9ee6` | | `accent` | Peach | `#ef9f76` | | `success` | Green | `#a6d189` | | `warning` | Yellow | `#e5c890` | | `error` | Red | `#e78284` | | `foreground` | Text | `#c6d0f5` | | `background` | Mantle | `#292c3c` | | `surface` | Surface0 | `#414559` | | `panel` | Surface1 | `#51576d` | | `dark` | — | `True` | `variables` dict (same keys the built-in Mocha theme sets): | Variable | Frappé color | Value | |------------------------------|---------------|----------------| | `input-cursor-foreground` | Crust | `#232634` | | `input-cursor-background` | Rosewater | `#f2d5cf` | | `input-selection-background` | Overlay2 30% | `#949cbb 30%` | | `border` | Lavender | `#babbf1` | | `border-blurred` | Surface2 | `#626880` | | `footer-background` | Surface1 | `#51576d` | | `block-cursor-foreground` | Base | `#303446` | | `block-cursor-text-style` | — | `none` | | `button-color-foreground` | Mantle | `#292c3c` | Note: `background` moves from Base to Mantle to match how the built-in Catppuccin themes layer surfaces — panels (Surface0/1) then sit visibly above the background. ### Widget styling (`src/hqt/tui/styles.tcss`) - Keep the existing `ProjectList`/`SessionList` layout rules. - Modal dialogs: `ProjectFormScreen` renames its container id to `#project-form-dialog`; `NewSessionScreen` keeps `#new-session-dialog`. Both get: centered (`align: center middle` on the modal screen), fixed width (~60 cells), `$surface` background, Lavender border, padding. - Panel headers (`#project-header`, `#session-header`): bold text on `$surface` background. ### Status symbol colors (`src/hqt/tui/widgets/session_list.py`) Color the status symbol via Rich markup with Frappé hex literals: | Status | Frappé color | Hex | |-----------|--------------|-----------| | `working` | Green | `#a6d189` | | `waiting` | Yellow | `#e5c890` | | `active` | Teal | `#81c8be` | | `idle` | Teal | `#81c8be` | | `dead` | Overlay0 | `#737994` | ## Testing - **Service** (`tests/test_services.py`): `update` renames and repaths; duplicate path raises `ValueError` and leaves the DB session usable (rollback); unknown id raises `ValueError`. - **TUI** (pilot tests, same style as `tests/test_tui.py`): - Pressing `e` with a project highlighted opens the form pre-filled with that project's name and path. - Submitting the edit form updates the DB and refreshes the list. - Pressing `e` with no project selected shows a warning notification. - The add-project flow still works through the renamed screen. - **Theme**: assert `app.current_theme.name == "catppuccin-frappe"` and spot-check resolved CSS variables (e.g. `background` is `#292c3c`, `border` variable is `#babbf1`) so a regression to auto-derived colors is caught. ## Out of scope - Archiving/un-archiving projects from the TUI. - Filesystem validation of project paths. - Moving or migrating existing tmux sessions when a path changes. - Light-mode (Latte) theme variant.