Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Roadmap and Future Work

Where flow is headed. The core tiling engine, the scrolling canvas, the floating space, and niri-style workspace switching are all implemented and exercised end-to-end. What remains is polish, the long tail of IPC commands, multi-monitor support, crash recovery, and the aspirational input-driven features.

Status at a Glance

AreaStatusNote
Tiling engine + scrolling canvas✅ Donemutate → project → animate pipeline
Window lifecycle hooks✅ Donecreate/destroy/minimize/restore/show-hide/foreground + STATECHANGE/NAMECHANGE
Dispatch surface✅ Mostlyfocus, swap, scroll, widths, center, monocle, close — see Remaining IPC stubs for the gaps
Floating space✅ Donetile↔float, centered placement, merged scroll+float batches
Workspaces✅ Done10 per monitor; switch + move-to animate; SwapWorkspace pending
Classification + learned rules✅ DoneDWM-cloak/iconic/Alt-Tab/owner filters + 4-layer rule pipeline
Animation✅ Done31 easing curves + CubicBezier, RetargetFromCurrent, MockBackend
Graceful-shutdown rescue✅ Donerescue_stranded_windows restores off-screen windows on clean exit
Mouse-drag follow on tiled windows🜂 Near-termtiled windows don’t react to user drags today — looks broken
SwapWorkspace animation🜂 Near-termonly workspace command still stubbed
Remaining IPC stubs🜂 Near-termPlaceAbove, QueryState, CheckConfig, SetConfigValue, ForgetApp/ForgetAllApps
Floating enhancements🜂 Near-termsmart placement, size memory, z-order, gap mgmt
Real cross-column MoveWindow🜂 Near-termLeft/Right aliased to swap-column; row-preserving move + floating nudge pending
Multi-monitor support◷ Mid-termVec<Monitor> modeled; constructor hard-codes one
Cloak parked windows (DWMWA_CLOAK)◷ Mid-termread side exists; write side missing
Watchdog + recovery snapshot◷ Mid-termnot started; gated on flow/flowd being complete
Input hooks (DragSession etc.)◷ Aspirationalsrc/input/ does not exist
Coexistence warning◷ Aspirationaldetect komorebi / GlazeWM at startup

Implemented Baseline

The mutate → project → animate pipeline, the window registry, and the Win32 hook thread are all in place and driving real window management:

  • Window lifecycle: create, destroy, minimize, restore, show/hide, foreground, plus the STATECHANGE (maximize/fullscreen recovery) and NAMECHANGE (late-title recovery) hooks. See Event Pipelines.
  • Dispatch surface: focus, per-window swap, column swap, semantic move, scroll, expand/shrink/set column width, center, monocle toggle, close window. See IPC & Watchdog.
  • Floating windows: tile↔float transitions (set-window float|tile|cycle), centered default placement, and merged scroll+float animation batches. See Floating Space.
  • Workspaces: ten workspaces per monitor; switch-workspace and move-to-workspace animate a vertical-packing switch (animate / teleport / skip partitioning). See Workspace Hierarchy.
  • Classification: DWM-cloak, iconic, Alt-Tab visibility, and owner pre-filters, plus the four-layer user/learned/default/default_action rule pipeline. See Classification & Learned Rules.
  • Animation: 31 named easing curves plus arbitrary CSS cubic-bezier, RetargetFromCurrent mid-flight retargeting, and a MockBackend for tests. See Animation.
  • Graceful-shutdown rescue: on clean exit the daemon runs rescue_stranded_windows, moving any off-screen window back onto the active monitor using its pre_manage_rect as the anchor. See IPC & Watchdog.

Near-term: Polish and Completeness

The wiring-heavy work is finished. The remaining near-term items close gaps in the IPC surface, make tiled windows feel responsive to direct manipulation, and round out the floating and workspace feature sets.

React to Mouse Dragging of Tiled Windows

Today the daemon does not react when the user drags a tiled window with the mouse: the window is moved by the OS/application, but flow neither follows the drag live nor re-applies the tiled geometry, so the window visually detaches from its column until the next layout-changing command snaps it back. This looks broken. The fix is to detect the drag (via the existing WinEvent surface — EVENT_OBJECT_LOCATIONCHANGE on a managed tiled window) and either re-assert the tiled position or transition the window to floating for the duration of the drag.

Workspace: SwapWorkspace

SwapWorkspace is the only workspace command still routed to unimplemented_command. SwitchWorkspace and MoveWindowToWorkspace are fully implemented with a vertical-packing animation model; SwapWorkspace needs its own animation decision because it exchanges two workspaces’ positions in the packed stack rather than sliding between them. Its protocol shape (SwapWorkspace { workspace_id }) is already locked in.

Remaining IPC Stubs

Several SocketMessage variants are declared and parsed by the CLI but return unimplemented_command on the daemon side. Their wire formats are stable so external tooling and keybindings can target them now:

CommandPurpose
PlaceAboveRaise the focused floating window’s z-order (SetWindowPos with HWND_TOPMOST/restore)
QueryStateRead-only introspection of daemon/registry state beyond QueryWindowsAll
CheckConfig / SetConfigValueRuntime config validation / mutation without a daemon restart (ReloadConfig is already implemented)
ForgetApp / ForgetAllAppsProgrammatic clearing of learned rules (today this requires hand-editing history-flow-rules.toml)

See Classification & Learned Rules for the learned-rules model that ForgetApp would expose programmatically.

Floating Window Enhancements

The floating space is functional; the open work is quality-of-life. See the “Future Work” section of Floating Space for the full list, summarised here:

  • Smart placement — cascade or offset new floats so they don’t fully overlap.
  • Per-window float size memory — remember each window’s last floating rect and restore it on subsequent tile→float transitions.
  • Z-order raisingPlaceAbove to bring the focused float to the top of the z-order (depends on the IPC stub above).
  • Floating gap management — reserve padding around floats at workspace edges.

MoveWindow: Real Cross-Column Move and Floating Nudge

MoveWindow’s up/down path is implemented (it delegates to dispatch_swap_window for an in-column swap). Two paths remain deferred:

  • Tiled left/right, row-preservingLeft/Right are currently aliased to SwapColumn (a column swap), not a true move that preserves row membership. The alias is intentional as a placeholder; a real cross-column extract-and-insert is the missing piece.
  • Floating, any direction — a pixel nudge by a configurable shift.

The branching structure already lives behind a single delegation point in dispatch_move_window, so these land without changing the IPC protocol or keybindings.

Mid-term

Multi-Monitor Support

The workspace hierarchy already models Vec<Monitor> with active_monitor: usize, and every IPC command routes through active_scrolling() so multi-monitor can land without touching call sites. The current constructor hard-codes a single monitor derived from get_primary_monitor_info() (src/daemon/new.rs). Expanding to multiple monitors requires iterating EnumDisplayMonitors / MonitorFromPoint + GetMonitorInfoW, building a Monitor per display, and adding flow dispatch focusmonitor / move-to-workspace <id> <monitor> plumbing.

Performance: Cloaking Off-Screen Windows

Parked (off-screen) tiled windows are kept one column-width beyond the nearest viewport edge so they animate smoothly when scrolled into view. They are, however, still rendered. A future optimisation can apply DWMWA_CLOAK (SetWindowCompositionAttribute) to parked windows so the DWM skips compositing them, reducing GPU work on large canvases. The classification pipeline already inspects DWMWA_CLOAKED; the write side is the new work.

Watchdog and Recovery-Snapshot Persistence

Not started. This is deliberately deferred until flow and flowd are feature-complete; it is tracked here as the intended design.

The planned design: flowd spawns a separate flow-watchdog helper process with --parent-pid and --recovery-path arguments. The watchdog polls the parent PID and, on unexpected exit, reads a flow-recovery.json snapshot and calls SetWindowPos to restore each window to its pre-manage geometry. The Window struct already carries pre_manage_rect for this purpose; the atomic write-to-temp-then-rename persistence path and the watchdog binary itself are what is missing. The watchdog must survive even if the daemon is corrupted, hung, or crashed, so it owns a separate process lifetime — the OS reaps the daemon while the watchdog keeps running, with no dependency on daemon state (it reads a file and calls Win32 APIs directly) and a minimal code path focused only on recovery. A Windows service was considered and rejected — it requires admin privileges for installation and adds a service-control-manager dependency; a child process is simpler and sufficient for a single-user desktop tool.

On graceful shutdown, no watchdog is needed: the daemon performs the equivalent restore inline via rescue_stranded_windows (already implemented — see Implemented Baseline). The watchdog only covers the crash case.

See IPC & Watchdog.

Aspirational: Deliberately Deferred

These features are acknowledged as valuable but not planned for the current development cycle.

InputInterceptor, DragSession, ResizeSession

Full mouse-driven tiling where Super + Left Mouse Button initiates a drag or resize session with layout snapping. This was described in the original spec but has no active implementation work — the src/input/ module does not exist. The daemon’s input surface today is the IPC pipe; mouse-driven tiling would add an in-process global input hook alongside the existing WinEvent hooks.

Super+LMB Mouse Gestures

Mouse gestures (drag to tile, drag to edge to snap, etc.) build on the DragSession infrastructure above and share its dependency on an unimplemented input hook.

Coexistence Warning

If komorebi, GlazeWM, or another tiling window manager is detected as running, flow should display a warning and ask the user to close the conflicting manager before using flow. Coexistence with another WM that also moves windows will produce unpredictable results.

Design Records

These sections record timeless constraints and deliberate design decisions, not pending work.

Keybindings Intentionally Removed

Keybinding handling was intentionally removed from both the config and the codebase. The rationale: external tools like AutoHotkey, PowerToys, or Komorebi’s keybinding layer are better at translating physical keypresses into IPC commands than a re-implemented keyboard hook. flow’s role is the layout engine and window manager — not the input layer. See Design Decisions for more on this separation of concerns.

Users map their preferred hotkeys to flow dispatch CLI calls via their chosen keybinding tool. This keeps flow’s attack surface small and avoids duplicating well-tested input infrastructure.

Known Win32 Limitations

These are not bugs — they are inherent properties of the Windows rendering model.

SetWindowPos vs DeferWindowPos

flow uses SetWindowPos (immediate positioning) rather than DeferWindowPos (batch positioning). DeferWindowPos batches multiple repositions into a single repaint, but it is atomic: a single elevated admin window (protected by UIPI) fails the entire batch with ERROR_ACCESS_DENIED. Individual SetWindowPos calls mean one failure logs a warning but does not block the remaining windows. See Animation for the per-backend rationale.

GetWindowRect Includes Invisible Borders

GetWindowRect returns a rect that includes a hidden ~7px border on the left, right, and bottom edges. This is not the visual rect of the window. flow works around this via the InvisibleBounds tracking in the registry. See the Window Registry chapter for how invisible bounds are measured and how visible_to_window() compensates.

Applications Own Their Render

flow can request a window position via SetWindowPos, but the application controls its own rendering. Some apps (especially UWP and Electron-based) may not immediately respect position changes or may reposition themselves autonomously. This is a fundamental constraint of the Windows windowing model.