ARCHITECTURE.md

Paneru Architecture

This document provides a high-level overview of Paneru's architecture for contributors. Paneru is a macOS window manager built using the Bevy Game Engine and its Entity Component System (ECS).

1. High-Level Overview

Paneru manages macOS windows as a sliding strip (inspired by Niri and PaperWM). The core design philosophy is Data-Driven/ECS: instead of managing windows as complex objects with internal state, we represent the "World" as a collection of simple data components (Windows, Displays, Workspaces) that are processed by systems.

The primary problem Paneru solves is providing a predictable, stable, and ergonomic tiling experience on macOS. By using Bevy's ECS, we gain:

  • Declarative Logic: Systems react to changes in window properties (e.g., Changed<Position>).
  • High Performance: Parallel system execution and efficient change detection.
  • Modularity: Functionality is divided into decoupled plugins and systems.

2. The Bevy Bridge

Bevy is typically used for games, so Paneru implements a custom bridge to interact with the macOS Window Server.

Event Ingestion (macOS -> ECS)

  1. Platform Layer: src/platform/ uses objc2 and AppKit to interface with macOS. It runs a native event loop or hooks into OS notifications.
  2. Event Channel: macOS events (mouse moves, window creations, space changes) are sent via a thread-safe mpsc channel.
  3. Pump System: The pump_events system (in src/ecs/systems.rs) reads from this channel during the PreUpdate phase and writes Bevy Messages or triggers Observers.
  4. Observers: Bevy Observers (primarily in src/ecs/triggers.rs, with focused domains such as session restore in src/ecs/restore.rs) react to these events to update the ECS World (e.g., spawning new Window entities or updating FocusedMarker).

State Synchronization (ECS -> macOS)

  1. Systems: Bevy systems (like layout::position_layout_windows) calculate the intended positions and sizes of windows based on the tiling logic.
  2. Commit Systems: In the PostUpdate phase, specialized systems like commit_window_position and commit_window_size identify windows that need updating.
  3. FFI Calls: These systems call methods on the Window trait object (implemented by WindowOS in src/manager/windows.rs), which performs the actual accessibility API calls to move or resize the physical macOS window.

Note: All AppKit/Accessibility calls must happen on the Main Thread. Paneru ensures this by using NonSend resources and executing critical synchronization systems on the main thread.

The Lua Worker (optional lua feature)

The embedded scripting runtime is the one deliberate exception to "everything interesting happens on the main thread". A handler is arbitrary user code of unbounded duration, and pump_events is itself main-thread-pinned, so running handlers inline meant a slow script stalled the frame clock. src/lua/worker.rs runs the interpreter on a dedicated thread instead:

  • Main → worker: dispatch_lua_events and command_lua_handler extract plain data (LuaEvent, StateSnapshot) out of the world and send it over an unbounded channel. Neither ever blocks.
  • Worker → main: drain_lua_outbox non-blockingly drains queued Commands and flash messages onto the command bus, one frame behind.
  • The query round-trip: paneru.query* still reads the live world. The worker sends a request carrying a reply channel and blocks on it; serve_lua_queries answers it from QueryStateParams in PreUpdate (before the pump) and again in PostUpdate. Shutdown drops the request queue, which unblocks any waiting handler with an error rather than a hang.

This is what keeps src/lua/runtime.rs free of any bevy import: it reaches the world only through an extract callback, which on the main thread is a direct query and on the worker is that round-trip.

3. Crate & Module Map

Directory / ModuleResponsibility Statement
src/ecs/layout.rsTiling algorithms, column management, and coordinate calculations.
src/ecs/systems.rsBevy systems for lifecycle management, event pumping, and state syncing.
src/ecs/params.rsHigh-level Bevy SystemParam abstractions for querying the World.
src/ecs/triggers.rsReactive event handlers (Observers) for OS and internal events.
src/ecs/restore.rsStartup session restore planning and application, including window matching, layout rebuilding, and restore grace-period handling.
src/ecs/workspace.rsManagement of virtual workspaces, display changes, and window movement between spaces.
src/ecs/scroll.rsInput handling for trackpad swipe gestures, inertia, and snapping.
src/ecs/focus.rsFocus management logic, including focus-follows-mouse and mouse-follows-focus.
src/ecs/state.rsPersistence of window layout and workspace state across restarts.
src/manager/OS-agnostic traits (WindowApi, ProcessApi) and their macOS implementations (WindowOS).
src/platform/Low-level macOS FFI, event loop integration, and workspace/input hooks.
src/config/Configuration parsing, validation, and hot-reloading logic.
src/commands.rsImplementation of CLI subcommands.
src/client.rsThe CLI side of the IPC protocol, and the only place JSON is produced.
src/reader.rsThe daemon side: owns the Mach service and turns requests into events.
crates/mach_ipcTyped channels over Mach ports; the transport itself. Async and blocking spellings of each operation, on SendPort/RecvPort.
src/overlay.rsLogic for drawing active window borders and inactive window dimming.

4. Key Data Entities

Components

  • Window: A wrapper around a macOS window handle (AXUIElement).
  • Display: Represents a physical monitor and its bounds.
  • LayoutStrip: A component attached to a Workspace/Display that manages the ordered list of Columns.
  • LayoutPosition / Position: The intended (layout) vs. actual (on-screen) coordinates.
  • Bounds / WidthRatio: The size of the window and its relative width in the tiling strip.
  • FocusedMarker: Identifies the currently focused window.
  • ActiveWorkspaceMarker: Identifies the currently active workspace.
  • SelectedVirtualMarker: Marks a virtual workspace that is currently selected by the user.
  • NativeFullscreenMarker: Marks a window that is in macOS native fullscreen mode.
  • Unmanaged: An enum identifying windows that are Floating, Minimized, or Hidden.
  • RepositionMarker / ResizeMarker: Used to signal that a window needs to be moved or resized.

Resources

  • WindowManager: A wrapper for the global window management state and OS bridge.
  • Config: The current user configuration.
  • PaneruState: The durable snapshot of managed layout, display, native workspace, and virtual workspace state used for recovery after restarts.
  • SessionRestore: A short-lived startup resource that keeps loaded state and restore timing active until the startup grace period expires.
  • MissionControlActive: A flag indicating if macOS Mission Control is visible (disabling tiling).
  • FocusFollowsMouse: Tracks which window should gain focus based on mouse position.

5. Architectural Invariants

  • Main Thread Only: Any interaction with objc2, AppKit, or Accessibility APIs must occur on the main thread.
  • ECS as Source of Truth: Tiling logic must operate on ECS components (WidthRatio, LayoutStrip). The physical macOS window state should be a reflection of the ECS state, not the other way around.
  • Pure Layout: Layout math (in layout.rs) should remain as pure as possible, operating on coordinates and ratios rather than directly calling OS APIs.
  • Bounded Restore: Saved session state is only consulted during startup restore. After SessionRestore expires, normal config and window-rule placement owns newly discovered windows.
  • Reactive Power Saving: Systems should use Bevy's reactive scheduling to avoid CPU usage when no windows are moving or events are occurring.

6. Session Restore

src/ecs/state.rs extracts and persists the restart snapshot. The state file is written atomically to paneru/state.json in the XDG state directory (~/.local/state/paneru/state.json on a default macOS setup) and is loaded during Bevy app setup.

src/ecs/restore.rs owns startup restore. It keeps the loaded PaneruState alive in SessionRestore for the configured grace period so applications have time to reopen their windows. As windows arrive, restore_window_state builds a restore plan from the saved state and the currently managed ECS windows.

Window matching prefers stable identity (window_id, pid, and bundle_id) and uses the conservative fallback identity only when it can do so unambiguously. The fallback includes bundle_id, window title when available, window identifier, role, and subrole. Saved windows that are missing at startup are ignored by default, and the restored layout is compacted around the matched windows.

Restore rebuilds LayoutStrips, virtual workspace rows, selected virtual workspace markers, and display associations. When the current macOS workspace to display mapping conflicts with saved display data, the current mapping is preferred; otherwise restore falls back to the saved display, then the active display, then any available display. Matched startup windows skip static [windows] placement so the saved session wins, while unmatched windows and post-grace windows follow normal config behavior.

7. Data Flow Diagram

mermaid
1graph TD
2 A[macOS Window Server] -->|Native Event| B(src/platform Layer)
3 B -->|mpsc Channel| C(pump_events System)
4 C -->|Bevy Event| D(Observer / Trigger)
5 D -->|Update ECS| E{ECS World}
6 E -->|Changed Component| F(Layout/Animation System)
7 F -->|Set RepositionMarker| E
8 E -->|PostUpdate| G(commit_window_position)
9 G -->|FFI Call| A
10 H[CommandReader] -->|Unix Socket| C
11 S[PaneruState file] -->|Startup load| R(session restore)
12 R -->|Rebuild saved strips| E
13 E -->|Periodic / exit save| S

8. Testing Strategy

  1. Pure Unit Tests: Located in src/tests.rs and alongside modules. These test layout math and configuration parsing without requiring a macOS environment.
  2. ECS Integration Tests: Use Bevy's App or World to drive systems in isolation. macOS APIs are typically mocked via the WindowApi and WindowManagerApi traits.
  3. Session Restore Tests: src/tests/session_restore.rs covers restore planning, missing-window compaction, startup grace behavior, config precedence, virtual workspace restoration, and multi-display fallback.
  4. FFI Verification: Manual or semi-automated tests on macOS to ensure the Accessibility API calls behave as expected with native windows.
  5. Agent Support: The AGENTS.md file provides project-specific guidance for AI agents to ensure contributions follow these architectural patterns.