Frontend app shell — GTK4/RELM4 layout & bootstrap #13

Open
opened 2026-04-16 11:31:09 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a user launching forge-agent, I want the application to start with a GTK4/libadwaita window showing the main layout (sidebar + content area with header bar), with the tokio backend running in a background thread and IPC channels connected, so that I see a functional shell ready for interaction.

Acceptance criteria

Bootstrap (app/src/main.rs)

  • Creates a tokio::runtime::Runtime in a separate thread
  • Creates IpcChannels (mpsc + broadcast)
  • Spawns backend::start(command_rx, event_tx) on the tokio runtime
  • Creates adw::Application with app ID dev.forge-agent.app
  • Passes IpcClient to the frontend build_ui() function
  • GTK4 runs on the main thread, tokio on the background thread
  • Sequence matches spec §14.1: load_config → init_logging → create channels → spawn backend → run GTK

Logging

  • tracing_subscriber initialized with env filter and file output to ~/.local/share/forge-agent/logs/
  • Console output for development (controlled by RUST_LOG env var)

AppModel (RELM4 root component, frontend/src/app.rs)

  • AppModel struct with: ipc: IpcClient, read_model: OrchestrationReadModel, active_view: ActiveView
  • AppMsg enum with variants: DomainEvent, TerminalOutput, GitStatusUpdated, SelectThread, SelectProject, SendMessage, SwitchView
  • ActiveView enum: Chat, Diff, Settings
  • On init: spawns an async task that receives AppEvent from IPC and forwards as AppMsg
  • On DomainEvent: updates local read_model via project() fold

GTK4 layout

  • AdwApplicationWindow as the root
  • AdwOverlaySplitView with collapsible sidebar
  • Sidebar: AdwNavigationPageAdwToolbarView with AdwHeaderBar (new thread button) and placeholder content area
  • Content: AdwNavigationViewAdwNavigationPage with AdwToolbarView, AdwHeaderBar (thread title, git branch indicator, diff/terminal toggle buttons)
  • Main content area: gtk::Paned (vertical) with placeholder for chat view (top) and message input (bottom)
  • Window follows system theme via libadwaita
  • Minimum window size is reasonable (e.g., 800x600)

Graceful shutdown

  • On window close / SIGTERM: sends a shutdown signal via IPC
  • Backend terminates provider sessions, flushes events, closes SQLite pool
  • tokio runtime shutdown with a 5s timeout

Tests

  • Test: application starts and shows the window (headless RELM4 test if possible)
  • Test: IPC bridge forwards events from backend to AppModel

Out of scope

  • Sidebar content (separate story)
  • Chat view content (separate story)
  • Terminal/Diff views (separate stories)

References

  • Spec §10.1 (AppModel)
  • Spec §10.2 (Layout GTK4 principal)
  • Spec §14 (Bootstrap & cycle de vie)
  • Spec §6.3 (Intégration GTK ↔ tokio)

Dependencies

  • Blocked by: #3 (load_config), #6 (IpcClient, channels), #7 (backend::start)
  • Blocks: #14, #15, #16, #17, #18
  • Branch off: issue-7-engine
  • Full graph: #21
## User story As a **user launching forge-agent**, I want the application to start with a GTK4/libadwaita window showing the main layout (sidebar + content area with header bar), with the tokio backend running in a background thread and IPC channels connected, so that I see a functional shell ready for interaction. ## Acceptance criteria ### Bootstrap (`app/src/main.rs`) - [ ] Creates a `tokio::runtime::Runtime` in a separate thread - [ ] Creates `IpcChannels` (mpsc + broadcast) - [ ] Spawns `backend::start(command_rx, event_tx)` on the tokio runtime - [ ] Creates `adw::Application` with app ID `dev.forge-agent.app` - [ ] Passes `IpcClient` to the frontend `build_ui()` function - [ ] GTK4 runs on the main thread, tokio on the background thread - [ ] Sequence matches spec §14.1: load_config → init_logging → create channels → spawn backend → run GTK ### Logging - [ ] `tracing_subscriber` initialized with env filter and file output to `~/.local/share/forge-agent/logs/` - [ ] Console output for development (controlled by `RUST_LOG` env var) ### AppModel (RELM4 root component, `frontend/src/app.rs`) - [ ] `AppModel` struct with: `ipc: IpcClient`, `read_model: OrchestrationReadModel`, `active_view: ActiveView` - [ ] `AppMsg` enum with variants: `DomainEvent`, `TerminalOutput`, `GitStatusUpdated`, `SelectThread`, `SelectProject`, `SendMessage`, `SwitchView` - [ ] `ActiveView` enum: `Chat`, `Diff`, `Settings` - [ ] On init: spawns an async task that receives `AppEvent` from IPC and forwards as `AppMsg` - [ ] On `DomainEvent`: updates local `read_model` via `project()` fold ### GTK4 layout - [ ] `AdwApplicationWindow` as the root - [ ] `AdwOverlaySplitView` with collapsible sidebar - [ ] Sidebar: `AdwNavigationPage` → `AdwToolbarView` with `AdwHeaderBar` (new thread button) and placeholder content area - [ ] Content: `AdwNavigationView` → `AdwNavigationPage` with `AdwToolbarView`, `AdwHeaderBar` (thread title, git branch indicator, diff/terminal toggle buttons) - [ ] Main content area: `gtk::Paned` (vertical) with placeholder for chat view (top) and message input (bottom) - [ ] Window follows system theme via libadwaita - [ ] Minimum window size is reasonable (e.g., 800x600) ### Graceful shutdown - [ ] On window close / SIGTERM: sends a shutdown signal via IPC - [ ] Backend terminates provider sessions, flushes events, closes SQLite pool - [ ] tokio runtime shutdown with a 5s timeout ### Tests - [ ] Test: application starts and shows the window (headless RELM4 test if possible) - [ ] Test: IPC bridge forwards events from backend to AppModel ## Out of scope - Sidebar content (separate story) - Chat view content (separate story) - Terminal/Diff views (separate stories) ## References - Spec §10.1 (AppModel) - Spec §10.2 (Layout GTK4 principal) - Spec §14 (Bootstrap & cycle de vie) - Spec §6.3 (Intégration GTK ↔ tokio) ## Dependencies - **Blocked by:** #3 (load_config), #6 (IpcClient, channels), #7 (backend::start) - **Blocks:** #14, #15, #16, #17, #18 - **Branch off:** `issue-7-engine` - **Full graph:** #21
claude-desktop added this to the v0.1.0 milestone 2026-04-16 11:31:09 +00:00
Sign in to join this conversation.
No description provided.