Skip to content

[codex] Add Cycle Monitors content rotation#302

Open
ABaldetti wants to merge 113 commits into
BarutSRB:mainfrom
ABaldetti:codex/cycle-monitors-content-rotation
Open

[codex] Add Cycle Monitors content rotation#302
ABaldetti wants to merge 113 commits into
BarutSRB:mainfrom
ABaldetti:codex/cycle-monitors-content-rotation

Conversation

@ABaldetti
Copy link
Copy Markdown

@ABaldetti ABaldetti commented May 6, 2026

Summary

This adds the new cycleMonitors command surface for rotating the visible contents of active monitor workspaces.

When multiple monitors have active workspaces, Cycle Monitors sorts the monitors by position and moves the normal managed windows/layout from each active workspace to the next monitor's active workspace, wrapping the last monitor back to the first. The workspace IDs and configured monitor assignments stay fixed.

Behavior

  • adds the public cycleMonitors action for hotkeys, runtime commands, IPC, CLI, settings, and command metadata
  • rotates normal managed tiled and floating windows between the currently active workspaces on connected monitors
  • preserves workspace-to-monitor configuration, so fixed main/secondary workspace rules remain authoritative
  • preserves Niri column layout state by rotating root children between fixed workspace roots instead of swapping roots
  • carries Niri viewport/session/focus state with moved content where applicable
  • excludes scratchpad and native fullscreen/suspended fullscreen windows from this first version
  • returns the existing no-op/not-found style result when there are fewer than two active workspaces or no eligible normal content

Tests And Docs

  • adds IPC/controller coverage for two-monitor content rotation, empty destination workspaces, floating windows, missing Niri-node fallback, no eligible content, and scratchpad/native fullscreen exclusion
  • adds Niri layout tests for two-workspace and three-workspace content rotation while preserving column structure
  • adds transcript coverage for the monitor workspace cycle flow
  • updates CLI/action/IPC/settings tests and the IPC/CLI docs for the new command

Validation

Passed:

  • git diff --check
  • Scripts/check-direct-mutation-callers.sh
  • make kernels-test
  • Scripts/check-kernel-abi-goldens.sh via make verify
  • Scripts/check-transcript-coverage.sh via make verify
  • swift test --filter 'WorkspaceManagerTests|IPCModelsTests|ActionCatalogTests|IPCCommandRouterTests|NiriLayoutEngineTests/rotateWorkspaceContents|MonitorWorkspaceCycleTests' --disable-sandbox

Blocked locally:

  • make verify was run and reached make build, then stopped in Scripts/build-preflight.sh because the local ignored Ghostty archive does not match the pinned digest in Scripts/build-metadata.env.
  • This PR does not modify Frameworks/GhosttyKit.xcframework or Scripts/build-metadata.env.
  • Expected digest: fc3f8846ff2fa307dbdeed618331455832bd42b43b1d9f98039a1c90b7a89e5b
  • Local digest: a91353aff21976810b14c472d650bcc63f0568821367e90866381473f8a50edf

Note: no app/package rebuild was performed after the final source cleanup.

Summary by CodeRabbit

  • New Features

    • Added "Cycle Monitors" command to rotate workspace contents across monitors, preserving assignments and leaving scratchpad/native-fullscreen windows in place.
  • Improvements

    • Hotkey import now preserves unassigned actions when applying partial settings.
    • Workspace projection logic updated to respect visible-monitor overrides where applicable.
  • Documentation

    • CLI docs updated with the new cycle-monitors command.
  • Tests

    • Added coverage and golden tests for the new command and behaviors.

Barut and others added 30 commits April 6, 2026 14:43
Replace the Niri axis solver, viewport geometry helpers, and monitor restore assignment matcher with Zig implementations behind a small C ABI surface.

Add a dedicated COmniWMKernels header target, a reproducible kernel build script, focused Swift and Zig regression coverage, and the documentation/build updates needed to rebuild and test the new library.
Stabilize the layout and CLI regression coverage after the Zig kernel port.

Enable animations explicitly in the shared layout-plan fixture, make the scratchpad async reveal test use the controller-local AX frame override after setup refreshes settle, add deterministic viewport settling in the Niri animation-toggle regressions, widen the injected CLI watch timeouts under aggregate load, and ignore Zig build cache output.
Pin shared build metadata for the macOS target, Zig toolchain, and Ghostty archive digest.

Add reusable preflight helpers plus canonical make targets for build, test, verify, and release-check workflows.

Refactor Zig and packaging scripts to consume the pinned metadata and fail fast on provenance or configuration mismatches.
Introduce per-target SwiftLint configs and target-specific thresholds so
the
stricter root lint policy can be rolled out without forcing legacy-heavy
modules to fail immediately.

Clean up the surfaced violations across Core, UI, IPC, Quake Terminal,
and
tests by replacing unsafe casts and `try!` usage, removing unnecessary
`async`, tightening access control, and marking unsupported
`init(coder:)`
paths unavailable.

Refresh the contributor docs with the supported `make build`, `make
test`,
`make verify`, and `make release-check` workflows, and align the AX/Niri
regressions with the cleanup.
Rewrite the scratchpad async fronting test to use the real async AX frame-apply path instead of the synchronous frameApplyOverrideForTests hook.

Add a small layout-plan test helper that installs an AppAXContext and disables the default synchronous override for async-sensitive tests.

This keeps the production scratchpad code unchanged while stabilizing the previously failing scratchpad test and the full Swift suite.
Move the pure Dwindle frame solver behind the COmniWMKernels C ABI and have Swift flatten the workspace tree into a kernel snapshot before reapplying solved frames to existing nodes.

This removes the Swift gap and min-size recursion, adds the new Zig solver entry point, and expands Dwindle regression coverage for single-window, fullscreen, gap, placeholder, and min-size behaviors.

Tested with: swift test --filter DwindleLayoutEngineTests
Fix typo in README for 'Scratchpad'
Stop using pending reveal transactions for workspaceInactive reveals so cross-workspace activation no longer re-hides windows or re-suppresses frame writes after transient verification misses.

Keep delayed reveal verification for floating and scratchpad-style async restores, and add regression coverage for the layout refresh, inactive-workspace activation, and workspace-bar focus paths.

Fixes BarutSRB#204
Fixes BarutSRB#207
Move Niri's deterministic bulk projection/layout slice out of Swift and into the Zig kernel library behind `omniwm_niri_layout_solve`.

Swift keeps ownership of the Niri tree, viewport state, monitor selection, and AppKit policy while flattening the current columns and windows into compact snapshot arrays, invoking one bulk solve, and applying the returned canonical/rendered rects, resolved spans, and hidden-edge classifications back onto the existing nodes.

This removes the old Swift-side projection math for container rects, window frames, visibility and overflow handling, and single-window aspect fitting, adds ABI coverage for empty and bounded caller-allocated buffers, and updates the architecture docs to document the Niri leaf-kernel boundary.

Tests:
- zig build test
- swift test --filter NiriLayoutEngineTests
- swift test --filter NiriConstraintSolverTests
- swift test --filter ViewportGeometryTests
- swift test --filter MonitorRestoreAssignmentsTests
- swift test --filter NiriLayoutKernelABITests
Move the deterministic reconcile reducer and restore-intent solve behind a compact Zig-backed C ABI.

Keep RuntimeStore, Planner, event normalization, trace recording, invariant validation, and runtime object mutation Swift-owned while StateReducer becomes a thin marshal/decode seam over omniwm_reconcile_plan and omniwm_reconcile_restore_intent.

Thread persisted hydration through the same solve, add direct ABI coverage plus Zig unit tests for reconcile behavior, and update architecture docs to document the new leaf-kernel boundary.

Testing:
- zig build test
- swift test --filter ReconcileStateTests
- swift test --filter ReconcileKernelABITests
- make kernels-test
- make test
Move keyboard focus border ownership and lifecycle decisions into the
BorderCoordinator reconcile loop so border visibility, ordering, and
fallback behavior stay consistent across focus changes, CGS events,
workspace transitions, and fullscreen/teardown paths.

- track managed and fallback owners with generation-scoped state,
  fallback leases, live-motion revalidation, and bounded trace records
  so stale frame/teardown events are ignored instead of reviving old
  borders
- derive ordering metadata and corner radius from validated window
  server state while keeping BorderManager and BorderWindow focused on
  rendering and preventing hidden config changes from replaying stale
  target state
- route WMController, AXEventHandler, layout refresh, workspace,
  service lifecycle, and Niri animation border updates through explicit
  reconcile sources and add regression tests plus architecture notes
  for the new behavior
Use one exact snappy spring preset for normal-mode animations.

Remove balanced/gentle presets and per-call spring tuning.

Make Reduce Motion resolve to the exact reducedMotion preset.

Switch Niri movement, workspace switching, overview animation, and window-close motion to plain snappy.

Add tests for spring config resolution, Niri config wiring, and close-animation settling.

Update architecture docs to reflect the new preset model.
Replace Dwindle's cubic window motion with a dedicated no-bounce spring
configuration and thread display refresh rate through workspace
snapshots
so motion settles consistently across monitors.

Snap canonical and animated Dwindle frames to physical pixels, seed new
split insertions from the split edge, and make fullscreen exclusive per
workspace to avoid overlapping fullscreen leaves.

Unify Dwindle settings application through the handler snapshot context,
remove the unused cubic animation path, and harden AX frame writes by
rejecting invalid target geometry without retrying.

Add regression coverage for Dwindle animation lifecycle, pixel snapping,
refresh-rate plumbing, insertion seeds, fullscreen exclusivity, and AX
invalid-frame rejection.

Verified with:
- swift test --filter
'DwindleLayoutEngineTests|SpringAnimationTests|AXManagerTests'
- swift test --skip-build
Record hotkeys directly from the responder chain instead of relying on a
local event monitor.

This lets the recorder capture command-based key equivalents before
AppKit swallows them, which fixes top-row workspace shortcuts on
Czech/QWERTZ and other non-QWERTY layouts.

Add regression tests that verify command plus top-row keys are stored by
physical key code, including through performKeyEquivalent. Fixes BarutSRB#171
Refresh resolved Niri monitor settings whenever global Niri config changes so monitors inheriting defaults update immediately from the settings window.

Route monitor-specific Niri refreshes through the same helper and add regression coverage for live center-focused-column and single-window-aspect-ratio propagation.
Accept AXStandardWindow subrole windows during AX enumeration even when the AX role is non-standard or missing, so Emacs-style windows remain tracked across active-space changes and wake/unlock full rescans.

Fixes BarutSRB#197
Route quake terminal hide/show through the shared focus pipeline so manual close restores the latest valid focus target instead of replaying a stale app snapshot.

Add regression coverage for manual close, focus-loss auto-hide, and external focus fallback paths.

Fixes BarutSRB#212
Move the deterministic Overview projection path out of Swift and into the
existing Zig kernels library.

Replace the generic workspace and Niri overview projection math in
OverviewLayoutCalculator with a bulk omniwm_overview_projection_solve FFI
call. The new kernel owns frame normalization, scale fitting, projected
window and column geometry, drop-zone generation, total content height,
and scroll bounds, while Swift keeps snapshot extraction, search,
navigation, thumbnails, and result application.

Extend the checked-in C ABI with explicit overview snapshot and result
structs, add the new Zig solver module, and update the universal archive
build step to run ranlib after lipo so the rebuilt static library links
cleanly.

Add direct ABI regression coverage plus extra Overview projection
characterization tests, and verify the change with:
- make kernels-build
- make kernels-test
- swift test --filter OverviewProjectionKernelABITests
- swift test --filter Overview
Replace the deterministic WindowRuleEngine base-decision tree with a compact Zig-backed window decision kernel behind omniwm_window_decision_solve.

Keep rule compilation, regex matching, title gating, metadata ownership, and manual overrides in Swift while reattaching workspace and rule-effect metadata after the kernel decode.

Add ABI coverage, decision regression tests, higher-level admission/manual-override coverage, and architecture notes for the new leaf-kernel boundary.
Seed native fullscreen restore snapshots before command-driven, direct-activation, and full-rescan fullscreen transitions so restored windows can replay their managed geometry after exiting AppKit fullscreen.

Keep restore records alive through the first relayout commit, suppress Niri and Dwindle animations during that restore pass, and force-apply the captured frame before clearing lifecycle state.

Expand AX event, refresh routing, and layout refresh tests to cover command-driven, direct-activation, replacement-token, and restore-plan finalization flows.
Normalize refresh, focus, and activation controller inputs into an explicit orchestration snapshot/event/plan boundary and route deterministic planning through OrchestrationCore.

Thin WMController, LayoutRefreshController, and AXEventHandler to adapter/executor roles, keep runtime ownership and platform effects in Swift, and add seam-level regression tests for refresh coalescing, focus supersede/defer, and native fullscreen restore activation planning.
Replace the Swift orchestration reducer with a Swift encoder/decoder
around
the new `omniwm_orchestration_step` C ABI. The Zig kernel now owns the
deterministic refresh and managed-focus state transitions, while Swift
continues to own macOS-facing effects like AX focus, borders, workspace
activation, refresh tasks, and retry scheduling.

Add the orchestration ABI surface to `COmniWMKernels`, including
flattened
refresh/focus snapshots, activation observations, window-removal
payloads,
decisions, ordered actions, and ABI layout reporting. The Swift wrapper
grows
caller-owned output buffers on `BUFFER_TOO_SMALL` and decodes the
returned
snapshot, decision, and action plan back into existing OmniWM types.

Mirror kernel-owned managed-focus state back into
`FocusBridgeCoordinator`
and `WorkspaceManager`, and route activation handling through normalized
kernel events. This moves retry budget progression, retry exhaustion,
owned-application fallback, native fullscreen restore activation, and
managed
activation confirmation into the reducer boundary.

Preserve refresh merge behavior across the port, including affected
workspace
sets, window-removal payloads, post-layout attachments, visibility
follow-ups,
and cancelled-refresh restart state. Expand orchestration and ABI tests
to
cover the new kernel boundary and update architecture docs for the
kernel-backed orchestration flow.
Barut and others added 23 commits April 30, 2026 19:04
Centralize OmniWM status item persistence repair so the main icon and Hidden Bar separator both recover from invalid AppKit preferred-position and visibility restore state before install.

Configure both owned status items as mandatory visible surfaces, clear hidden visibility preferences during unsafe-ordering rebuilds, and document targeted recovery for older builds without resetting settings.toml.

Fixes BarutSRB#277.

Refs BarutSRB#188.

Verification:

- make build

- swift test --filter MenuBarRecoveryTests

- git diff --check
Replace global watermark rejection for delayed runtime callbacks with scoped validation against the pending focus request, frame write, or affected workspace transition.

Record pending frame writes as runtime transactions with request ids, route workspace transition post-actions through WMRuntime, and expand workspace navigation affected-workspace sets so unrelated mutations no longer cancel valid confirmations.

Add Swift and Zig regressions for focus confirmation, AX frame write outcome, workspace transition, and navigation scope behavior.
Add direct runtime-toggle coverage for workspace layout round trips so the user-facing toggle path preserves graph membership, focus, monitor assignment, and Niri viewport state.

Rename the graph audit helper to describe the layout-switch invariant it enforces and document the intentionally ignored fields.
Preserve manually moved app workspace placement during automatic rule reevaluation and sibling window admission while keeping explicit rule apply as the path that reapplies workspace defaults to existing windows.

Add regression coverage for new sibling windows, automatic reevaluation, and explicit IPC rule apply behavior. Update CLI docs and README to describe initial default placement semantics.

Tests: ./Scripts/build-preflight.sh build debug && LIBRARY_PATH="/Users/barut/OmniWM/OmniWM/Frameworks/GhosttyKit.xcframework/macos-arm64_x86_64" swift test --filter "AXEventHandlerTests/newSiblingWindowFollowsMovedAppWorkspaceWhileAutomaticReevaluationPreservesMovedWindow|IPCRuleRouterTests/applyPidExplicitlyReappliesWorkspaceRuleToExistingWindows|IPCRuleRouterTests/applyFocusedReevaluatesManagedFocusAndReturnsUpdatedRulesProjection|IPCRuleRouterTests/applyWindowSupportsExplicitOpaqueIdsAndStableValidationFailures"

Fixes BarutSRB#274
Restore workspace-inactive floating windows when their workspace becomes
active, let rescue clear stale workspace-inactive hidden records, and
trust verified AX frame readback when an app reports a setter error
after applying the requested frame.

Fixes BarutSRB#224.
Route focused floating-window workspace transfers through workspace graph reassignment instead of requiring a layout-engine node. Preserve floating mode/state across IPC and runtime move-to-workspace flows, and keep layout-plan tests isolated from live platform hooks.

Closes BarutSRB#250
Stop clamping the cursor back to the last monitor when movement occurs outside the configured mouse-warp axis. Valid edge warps still run, while ordinary cross-monitor movement now updates the last monitor instead of bouncing the cursor.

Replace clamp-specific mouse warp tests with regressions for permissive non-axis movement and outside-all-monitor no-op behavior.
…s SLS misses

Some Electron-based apps (verified: WeChat / com.tencent.xinWeChat and
ChatGPT Atlas / com.openai.atlas) are silently omitted by
SLSWindowQueryWindows(cid, [], _) — the empty-array enumeration that
queryAllVisibleWindows() relies on. Their windows are returned when
queried by WID directly, but never appear in the empty-array enumeration
regardless of the flags bitmap (verified: 0x0, 0x1, 0x2, 0x4, 0x8, 0x10,
0x20, 0x80, 0x100, 0x200, 0x400, 0x800, 0xffffffff).

Because the affected pid never makes it into pidsWithWindows, the rest
of OmniWM never sees these apps at all — no admission, no AX context,
no layout. Restarting OmniWM does not help.

Augment pidsWithWindows from the public CGWindowList API as a
complement, restricted to layer==0 (regular app windows) and alpha>0
(actually rendered) to avoid Dock / menubar / wallpaper / tooltips.
This is additive only — apps already discovered via SLS are unaffected.

Closes BarutSRB#290
…XValue error wrappers

AXUIElementCopyMultipleAttributeValues with options=0 returns *typed
error wrappers* (not nil) for missing attributes. Empirically observed
for AXFullScreenButton on bilibili (com.bilibili.bilibiliPC): an
AXValue containing kAXValueAXErrorType (error -25212 / kAXErrorNoValue),
which is neither NSError nor CFError and slips past the original
nil/NSError filter.

The downstream code only ever calls hasResolvedAttribute() on
close/fullscreen/zoom/minimize button attributes — all of which must
be AXUIElements when present — so accept ONLY genuine AXUIElement
values. Anything else (AXValue error wrapper, CFError, etc.) is
correctly treated as 'attribute absent / unusable'.

Before the fix, the AXValue wrapper was misclassified as 'has button',
which then tripped the type-check guard at line ~538 and set
attributeFetchSucceeded=false on what is otherwise a perfectly normal
window. The heuristic kernel saw attributeFetchFailed and returned
disposition=undecided, leaving bilibili windows deferred and floating.

After the fix:
  attributeFetchSucceeded: True  (was: False)
  admissionOutcome: tracked-floating  (was: deferred)
  heuristicReasons: ['missingFullscreenButton']  (was: ['attributeFetchFailed'])

The remaining 'floating' disposition is now a legitimate heuristic
decision ('no fullscreen button → probably a tool window') that users
can override with a per-app rule (layout = "tile").

Verified no regression: re-tested every previously-working app
(Claude, Spotify, Cursor, Mail, Calendar, Ghostty, WeChat) — none
changed disposition. Working apps always returned a real AXUIElement
for present buttons, which the new strict check still accepts.

Closes BarutSRB#291
Drop the static-viewport-preserving Niri window-removal path, including reveal-side/recovery policy fields and layout focus intents.

Remove the AX same-app focus preemption/suppression hooks that fed that recovery path, simplify orchestration/topology ABI payloads, and prune the regression tests tied to the removed policy.
Make immediate relayouts check for real Niri viewport/window/column animation work before taking the scroll-animation path, pruning stale display registrations when none remains.

When active Niri animations are cancelled, settle viewport and engine motion, apply final frames on demand, and unregister display-link work. Cancel existing animations when starting an Alt-drag move and add regressions for final-frame application and stale scroll registrations.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

Adds a new cycleMonitors command and wiring to rotate workspace contents across monitor-connected workspaces (with Niri bulk-rotate and fallback reassignment paths), updates hotkey canonicalization and projection visibility logic, augments IPC/CLI/docs, expands tests, and fixes allowlist/boundary reporting in a maintenance script.

Changes

Monitor Workspace Cycling Feature

Layer / File(s) Summary
Input & Action Definition
Sources/OmniWM/Core/Input/InputBindingTrigger.swift, Sources/OmniWM/Core/Input/ActionCatalog.swift, Sources/OmniWMIPC/IPCModels.swift
New cycleMonitors trigger, action spec, and IPC command/request types added for user invocation and automation.
Hotkey Binding Canonicalization
Sources/OmniWM/Core/Input/HotkeyBinding.swift, Sources/OmniWM/Core/Config/SettingsStore.swift
HotkeyBindingRegistry.canonicalize now accepts HotkeyBinding entries; SettingsStore.applyExport canonicalizes imported hotkey bindings on apply.
Core Cycling Logic
Sources/OmniWM/Core/Controller/WorkspaceNavigationHandler.swift, Sources/OmniWM/Core/Layout/Niri/NiriLayoutEngine+WorkspaceOps.swift
cycleMonitors(source:) implemented; rotateWorkspaceContents(_:) added to Niri engine to rotate column contents and ensure at least one column per workspace; includes filtering of non-movable entries and Niri/manual reassignment paths.
Workspace Projection Visibility
Zig/omniwm_kernels/src/workspace_session.zig
Added visible_overrides_configured parameter to projection population so certain operations use live visible monitor IDs when configured.
Command Routing & Runtime Wiring
Sources/OmniWM/Core/Runtime/WMCommand.swift, Sources/OmniWM/Core/Runtime/WMRuntime.swift, Sources/OmniWM/Core/Runtime/RuntimeControllerOperations.swift, Sources/OmniWM/IPC/IPCCommandRouter.swift
cycleMonitors layout mutation case added and routed through typed command promotion, runtime dispatch, and IPC router to invoke the workspace navigation handler.
Tests
multiple test files under Tests/OmniWMTests/*
Comprehensive tests added/updated: action spec, CLI parsing, IPC router scenarios (rotation, empty target, scratchpad/fullscreen behavior, fallbacks, not-found/disabled), IPC model round-trips, hotkey dispatch, Niri engine rotation tests, kernel projection tests, and end-to-end transcript golden tests.
Documentation & Manifest
docs/IPC-CLI.md, Zig/omniwm_kernels/src/ipc_manifest.json
CLI docs added for command cycle-monitors; IPC manifest updated (and windows query floating selector removed).

Script Maintenance

Layer / File(s) Summary
Allowlist & Boundary Rule Reporting
Scripts/check-direct-mutation-callers.sh
print_budget_report(), allowed_rule_for(), and layout_allowed_count_for() now guard iteration over ALLOWLIST_RULES/OWNERSHIP_BOUNDARY_RULES/LAYOUT_CONSUMER_ALLOWLIST, printing (empty) when appropriate and returning guarded counts to fix reporting and matching paths.

Sequence Diagram

sequenceDiagram
    participant User as rgba(64,64,255,0.5)
    participant CLI_IPC as rgba(0,128,128,0.5)
    participant WMRuntime as rgba(0,200,0,0.5)
    participant WorkspaceNavHandler as rgba(200,100,0,0.5)
    participant NiriLayoutEngine as rgba(128,0,128,0.5)
    participant WorkspaceSession as rgba(200,0,0,0.5)

    User->>CLI_IPC: cycle-monitors command
    CLI_IPC->>WMRuntime: promote to layoutMutationAction.cycleMonitors
    WMRuntime->>WorkspaceNavHandler: cycleMonitors(source)
    WorkspaceNavHandler->>WorkspaceNavHandler: gather active workspaces & eligible entries
    WorkspaceNavHandler->>WorkspaceNavHandler: save Niri viewport state
    alt can bulk-rotate with Niri
        WorkspaceNavHandler->>NiriLayoutEngine: rotateWorkspaceContents(workspaceIds)
        NiriLayoutEngine-->>WorkspaceNavHandler: success/failure
    else fallback reassignment
        WorkspaceNavHandler->>WorkspaceNavHandler: manually move/reassign windows
    end
    WorkspaceNavHandler->>WorkspaceSession: apply session snapshot/patches (viewport/focus)
    WorkspaceNavHandler-->>CLI_IPC: ExternalCommandResult.executed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 With whiskers twitching, windows leap and spin,

Monitors trade their workspace kin,
Scratchpad stays, fullscreen holds fast,
Niri shifts columns, rotations cast,
A joyful hop — the carousel begins.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] Add Cycle Monitors content rotation' clearly and specifically describes the main change: adding a new command/feature to rotate monitor contents, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ABaldetti ABaldetti changed the title [codex] Implement cycle monitors content rotation [codex] Add Cycle Monitors content rotation May 7, 2026
@ABaldetti ABaldetti marked this pull request as ready for review May 7, 2026 00:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift (1)

448-513: ⚡ Quick win

Split this test into arrange/act/assert helpers to satisfy function length lint and keep intent crisp.

The test is solid, but it exceeds the configured body-length threshold and is getting noisy to scan. Extract monitor/workspace fixture creation or assertions into small local helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift` around lines 448 -
513, The test `project treats visible monitor cycle as current projection
without changing homes` is too long; split into arrange/act/assert helpers to
satisfy lint: extract monitor and workspace setup into an "arrange" helper (use
makeWorkspaceSessionUUID, makeWorkspaceSessionMonitor,
makeWorkspaceSessionWorkspace and WorkspaceSessionKernelStringTable), move the
call to withWorkspaceSessionOutput/callWorkspaceSessionKernel into an "act"
helper that returns status, output and projectionBuffer, and put the expectation
checks (the `#expect` lines validating output.outcome and projectionBuffer
entries) into an "assert" helper; replace the inlined arrays and assertions in
the test body with calls to these small helpers so the test function body is
short and intent remains clear.
Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift (2)

8-20: 💤 Low value

Drop unused async throws to satisfy async_without_await.

transcriptIsGoldenStable does not call any await or try expression; SwiftLint flags this. The second test legitimately needs async throws, but this one can be a plain func.

♻️ Proposed change
-    `@Test` `@MainActor` func transcriptIsGoldenStable() async throws {
+    `@Test` `@MainActor` func transcriptIsGoldenStable() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift`
around lines 8 - 20, transcriptIsGoldenStable currently declares "async throws"
but contains no await/try; remove the unnecessary async throws from the function
signature of transcriptIsGoldenStable (leave the `@Test` and `@MainActor` attributes
and body intact) so it becomes a synchronous test function; do not change the
other test that legitimately requires async throws (e.g., the other test in this
file that relies on await/try).

58-64: ⚡ Quick win

Strengthen post-cycle assertions to verify window relocation.

The current end-state checks confirm only that workspace-to-monitor bindings are unchanged, which is the unconditional invariant of the feature. Consider also asserting that primaryToken now resolves to workspace "2" and secondaryToken to workspace "1", so a regression that no-ops the rotation still satisfies the monitor binding invariant but fails the relocation assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift`
around lines 58 - 64, Add assertions after driver.run() that verify the window
tokens were actually relocated: assert that primaryToken now resolves to
workspaceTwoId and secondaryToken to workspaceOneId. Use the same
runtime/controller APIs used elsewhere in the test to resolve a token's
workspace (e.g., runtime.controller.workspaceManager.workspaceId(for:
primaryToken) == workspaceTwoId and
runtime.controller.workspaceManager.workspaceId(for: secondaryToken) ==
workspaceOneId), placing these checks alongside the existing activeWorkspace(on:
primaryMonitorId)/activeWorkspace(on: secondaryMonitorId) expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift`:
- Around line 8-20: transcriptIsGoldenStable currently declares "async throws"
but contains no await/try; remove the unnecessary async throws from the function
signature of transcriptIsGoldenStable (leave the `@Test` and `@MainActor` attributes
and body intact) so it becomes a synchronous test function; do not change the
other test that legitimately requires async throws (e.g., the other test in this
file that relies on await/try).
- Around line 58-64: Add assertions after driver.run() that verify the window
tokens were actually relocated: assert that primaryToken now resolves to
workspaceTwoId and secondaryToken to workspaceOneId. Use the same
runtime/controller APIs used elsewhere in the test to resolve a token's
workspace (e.g., runtime.controller.workspaceManager.workspaceId(for:
primaryToken) == workspaceTwoId and
runtime.controller.workspaceManager.workspaceId(for: secondaryToken) ==
workspaceOneId), placing these checks alongside the existing activeWorkspace(on:
primaryMonitorId)/activeWorkspace(on: secondaryMonitorId) expectations.

In `@Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift`:
- Around line 448-513: The test `project treats visible monitor cycle as current
projection without changing homes` is too long; split into arrange/act/assert
helpers to satisfy lint: extract monitor and workspace setup into an "arrange"
helper (use makeWorkspaceSessionUUID, makeWorkspaceSessionMonitor,
makeWorkspaceSessionWorkspace and WorkspaceSessionKernelStringTable), move the
call to withWorkspaceSessionOutput/callWorkspaceSessionKernel into an "act"
helper that returns status, output and projectionBuffer, and put the expectation
checks (the `#expect` lines validating output.outcome and projectionBuffer
entries) into an "assert" helper; replace the inlined arrays and assertions in
the test body with calls to these small helpers so the test function body is
short and intent remains clear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30e846c6-170f-41b5-b4e2-97631faf7109

📥 Commits

Reviewing files that changed from the base of the PR and between ea732a3 and 846f04d.

📒 Files selected for processing (26)
  • Scripts/check-direct-mutation-callers.sh
  • Sources/OmniWM/Core/Config/SettingsStore.swift
  • Sources/OmniWM/Core/Controller/WorkspaceNavigationHandler.swift
  • Sources/OmniWM/Core/Input/ActionCatalog.swift
  • Sources/OmniWM/Core/Input/HotkeyBinding.swift
  • Sources/OmniWM/Core/Input/InputBindingTrigger.swift
  • Sources/OmniWM/Core/Layout/Niri/NiriLayoutEngine+WorkspaceOps.swift
  • Sources/OmniWM/Core/Runtime/RuntimeControllerOperations.swift
  • Sources/OmniWM/Core/Runtime/WMCommand.swift
  • Sources/OmniWM/Core/Runtime/WMRuntime.swift
  • Sources/OmniWM/IPC/IPCCommandRouter.swift
  • Sources/OmniWMIPC/IPCModels.swift
  • Tests/OmniWMTests/ActionCatalogTests.swift
  • Tests/OmniWMTests/CLIParserTests.swift
  • Tests/OmniWMTests/IPCCommandRouterTests.swift
  • Tests/OmniWMTests/IPCModelsTests.swift
  • Tests/OmniWMTests/KeyboardHotkeyThroughRuntimeTests.swift
  • Tests/OmniWMTests/NiriLayoutEngineTests.swift
  • Tests/OmniWMTests/SettingsStoreTests.swift
  • Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift
  • Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTranscript.swift
  • Tests/OmniWMTests/WorkspaceManagerTests.swift
  • Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift
  • Zig/omniwm_kernels/src/ipc_manifest.json
  • Zig/omniwm_kernels/src/workspace_session.zig
  • docs/IPC-CLI.md
💤 Files with no reviewable changes (1)
  • Tests/OmniWMTests/WorkspaceManagerTests.swift

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift`:
- Around line 463-476: In assertVisibleMonitorCycleProjection, before asserting
home_monitor_id, effective_monitor_id, and projected_monitor_id on each
projectionBuffer entry, add explicit assertions that has_home_monitor_id,
has_effective_monitor_id, and has_projected_monitor_id are true for
result.projectionBuffer[0] and result.projectionBuffer[1]; i.e., assert the
corresponding has_* flags on each entry (has_home_monitor_id,
has_effective_monitor_id, has_projected_monitor_id) first, then verify the ID
values as currently done in assertVisibleMonitorCycleProjection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7298f1be-0c67-4bb7-91ca-04e30698666e

📥 Commits

Reviewing files that changed from the base of the PR and between 846f04d and 0367a09.

📒 Files selected for processing (2)
  • Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift
  • Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift
✅ Files skipped from review due to trivial changes (1)
  • Tests/OmniWMTests/Transcripts/Goldens/MonitorWorkspaceCycleTests.swift

Comment on lines +463 to +476
private func assertVisibleMonitorCycleProjection(
_ result: VisibleMonitorCycleProjectionResult
) {
#expect(result.status == OMNIWM_KERNELS_STATUS_OK)
#expect(result.output.outcome == UInt32(OMNIWM_WORKSPACE_SESSION_OUTCOME_APPLY))
#expect(result.projectionBuffer.count == 2)
guard result.projectionBuffer.count == 2 else { return }
#expect(result.projectionBuffer[0].home_monitor_id == 10)
#expect(result.projectionBuffer[0].effective_monitor_id == 10)
#expect(result.projectionBuffer[0].projected_monitor_id == 20)
#expect(result.projectionBuffer[1].home_monitor_id == 20)
#expect(result.projectionBuffer[1].effective_monitor_id == 20)
#expect(result.projectionBuffer[1].projected_monitor_id == 10)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add has_* flag assertions before trusting projection field values.

The assertion helper checks home_monitor_id, effective_monitor_id, and projected_monitor_id directly, but never asserts the corresponding presence flags (has_home_monitor_id, has_effective_monitor_id, has_projected_monitor_id). Every other projection-validating block in this file (e.g. Lines 611–615, 673–677, 1162–1166) guards the value checks with an explicit has_* assertion first. Without them, this test could pass even if the kernel emits the "right" field values but leaves has_* as 0 — or if a zero-initialised entry coincidentally matches.

🛡️ Proposed fix
     `#expect`(result.projectionBuffer.count == 2)
     guard result.projectionBuffer.count == 2 else { return }
+    `#expect`(result.projectionBuffer[0].has_home_monitor_id == 1)
     `#expect`(result.projectionBuffer[0].home_monitor_id == 10)
+    `#expect`(result.projectionBuffer[0].has_effective_monitor_id == 1)
     `#expect`(result.projectionBuffer[0].effective_monitor_id == 10)
+    `#expect`(result.projectionBuffer[0].has_projected_monitor_id == 1)
     `#expect`(result.projectionBuffer[0].projected_monitor_id == 20)
+    `#expect`(result.projectionBuffer[1].has_home_monitor_id == 1)
     `#expect`(result.projectionBuffer[1].home_monitor_id == 20)
+    `#expect`(result.projectionBuffer[1].has_effective_monitor_id == 1)
     `#expect`(result.projectionBuffer[1].effective_monitor_id == 20)
+    `#expect`(result.projectionBuffer[1].has_projected_monitor_id == 1)
     `#expect`(result.projectionBuffer[1].projected_monitor_id == 10)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/OmniWMTests/WorkspaceSessionKernelABITests.swift` around lines 463 -
476, In assertVisibleMonitorCycleProjection, before asserting home_monitor_id,
effective_monitor_id, and projected_monitor_id on each projectionBuffer entry,
add explicit assertions that has_home_monitor_id, has_effective_monitor_id, and
has_projected_monitor_id are true for result.projectionBuffer[0] and
result.projectionBuffer[1]; i.e., assert the corresponding has_* flags on each
entry (has_home_monitor_id, has_effective_monitor_id, has_projected_monitor_id)
first, then verify the ID values as currently done in
assertVisibleMonitorCycleProjection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants