Ashiedu's picture
Sync unified workbench
0490201 verified
# 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