Spaces:
Runtime error
Runtime error
| # T-014: Window Management β Resize Constraints, Always-on-Top, Multi-Monitor | |
| **Type:** Task | **Phase:** 0 | **Autonomy:** `agent:autonomous` | **Stack:** `stack:rust` `stack:typescript` | |
| **Version:** v0.1 | **Iteration:** iter-1 | **Effort:** XS (1 hour) | |
| > β οΈ **Scope:** Add Tauri window management commands callable from the UI: toggle always-on-top, move to monitor, get current monitor info. Min/max size constraints are already set in `tauri.conf.json` (T-001). This task adds the runtime IPC surface for window behaviour the frontend needs. | |
| --- | |
| ## Prerequisites | |
| - [ ] T-001 merged β window exists with correct min/max constraints in `tauri.conf.json` | |
| - [ ] T-002 merged β IPC command pattern established | |
| ## Acceptance Criteria | |
| - [ ] `window_set_always_on_top(enable: bool)` IPC command works | |
| - [ ] `window_get_monitors()` returns list of connected monitors with position/size | |
| - [ ] `window_move_to_monitor(index: u32)` centres the window on the specified monitor | |
| - [ ] All three commands registered in `tauri::generate_handler![]` and capability file | |
| - [ ] TypeScript wrappers added to `src/ipc/commands.ts` | |
| - [ ] `cargo check --workspace` β zero errors | |
| ## `kansas/src/commands/window.rs` | |
| ```rust | |
| use tauri::{AppHandle, Manager, Window}; | |
| use serde::Serialize; | |
| use crate::ipc::error::IpcError; | |
| #[derive(Debug, Serialize)] | |
| #[serde(rename_all = "camelCase")] | |
| pub struct MonitorInfo { | |
| pub index: u32, | |
| pub name: String, | |
| pub width: u32, | |
| pub height: u32, | |
| pub x: i32, | |
| pub y: i32, | |
| pub scale_factor: f64, | |
| pub is_primary: bool, | |
| } | |
| #[tauri::command] | |
| pub fn window_set_always_on_top( | |
| enable: bool, | |
| window: Window, | |
| ) -> Result<(), IpcError> { | |
| window.set_always_on_top(enable) | |
| .map_err(|e| IpcError::Internal(e.to_string())) | |
| } | |
| #[tauri::command] | |
| pub fn window_get_monitors( | |
| app: AppHandle, | |
| ) -> Result<Vec<MonitorInfo>, IpcError> { | |
| let monitors = app.available_monitors() | |
| .map_err(|e| IpcError::Internal(e.to_string()))?; | |
| let primary = app.primary_monitor() | |
| .ok().flatten() | |
| .and_then(|m| m.name().map(|s| s.to_string())); | |
| Ok(monitors.into_iter().enumerate().map(|(i, m)| { | |
| let pos = m.position(); | |
| let size = m.size(); | |
| MonitorInfo { | |
| index: i as u32, | |
| name: m.name().unwrap_or("Unknown").to_string(), | |
| width: size.width, | |
| height: size.height, | |
| x: pos.x, | |
| y: pos.y, | |
| scale_factor: m.scale_factor(), | |
| is_primary: primary.as_deref() == m.name(), | |
| } | |
| }).collect()) | |
| } | |
| #[tauri::command] | |
| pub fn window_move_to_monitor( | |
| index: u32, | |
| window: Window, | |
| app: AppHandle, | |
| ) -> Result<(), IpcError> { | |
| let monitors = app.available_monitors() | |
| .map_err(|e| IpcError::Internal(e.to_string()))?; | |
| let monitor = monitors.get(index as usize) | |
| .ok_or_else(|| IpcError::InvalidParam(format!("Monitor index {index} out of range")))?; | |
| let pos = monitor.position(); | |
| let size = monitor.size(); | |
| let win_size = window.outer_size() | |
| .map_err(|e| IpcError::Internal(e.to_string()))?; | |
| let x = pos.x + ((size.width as i32 - win_size.width as i32) / 2); | |
| let y = pos.y + ((size.height as i32 - win_size.height as i32) / 2); | |
| window.set_position(tauri::PhysicalPosition::new(x, y)) | |
| .map_err(|e| IpcError::Internal(e.to_string())) | |
| } | |
| ``` | |
| Register in `main.rs` handler list and add to `capabilities/default.json`: | |
| ```json | |
| "synesthesia:allow-window-set-always-on-top", | |
| "synesthesia:allow-window-get-monitors", | |
| "synesthesia:allow-window-move-to-monitor" | |
| ``` | |
| Add to `src/ipc/commands.ts`: | |
| ```typescript | |
| export const windowSetAlwaysOnTop = (enable: boolean): Promise<void> => | |
| invoke('window_set_always_on_top', { enable }); | |
| export const windowGetMonitors = (): Promise<MonitorInfo[]> => | |
| invoke('window_get_monitors'); | |
| export const windowMoveToMonitor = (index: number): Promise<void> => | |
| invoke('window_move_to_monitor', { index }); | |
| ``` | |
| --- | |
| ## GitHub CLI | |
| ```bash | |
| gh issue create \ | |
| --title "T-014: Window management β always-on-top, multi-monitor, resize constraints" \ | |
| --label "type:task,stack:rust,stack:typescript,agent:autonomous,priority:medium,status:ready,day:1" \ | |
| --body-file T-014.md | |
| ``` | |
| **Parent:** GENESIS | **Blocks:** none | **Blocked By:** T-001, T-002 | |