Skip to content

fix(rules): apply title rules when the title arrives just after opening - #262

Merged
qeude merged 6 commits into
mainfrom
fix/rules-late-title
Nov 11, 2026
Merged

qeude merged 6 commits into
mainfrom
fix/rules-late-title

Conversation

@qeude

@qeude qeude commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

Problem

A [[rules]] entry with title or title_regex was only evaluated when Defi first discovered a window. An app that sets its title just after opening never matched.

Change

When a newly opened window gets its first new, non-empty title within 1 s of opening, Defi evaluates its rules again, once. If the placement rules changed, it places the window again as if it had opened with that title. The late title comes from the existing snapshot path that already produces window-changed, with no new Accessibility read or timer.

  • Bounded: this happens once, on the first title change, within lateTitleRuleGrace (1 s). A title that changes later, such as a browser tab's, never moves a window. Windows already open when Defi starts are skipped.
  • Never undoes the user: the window's placement at opening (workspace, floating, column and its index, a floating window's frame) is recorded. A rule that names no workspace or display keeps the window on its workspace. Native fullscreen ends the candidate. Any change to it in the meantime (move, resize, float toggle, stacking) cancels the second evaluation.
  • Applies late: workspace, monitor, window, initial_column_width, intrinsic_size.
  • Not applied late: open_focused and follow_focus. Focus was already decided at opening. A focused window follows to its new place; any other window moves without taking focus. The daemon runs its placement step for the late window (a floating window's frame is moved onto the rule's display), but not its opening focus rules. include_initial_width_in_cycle is read at cycle time anyway.

Niri reads its open-* rules once, when the window first maps. Its dynamic rules are re-read on every title change. This change adds a short, one-time window for the case where the title arrives just after the window maps.

CONFIGURATION.md describes the new behaviour in place of "a title set later never matches".

Proof

  • WindowLifecycleTests: "A title set right after opening applies its rules" fails on main (3 expectations) and passes here. "A late title never overrides the user or a window in use" covers a title after the grace, a float toggle, a column move, and a non-matching first title, on the now: virtual clock. "A late title rule on the same workspace keeps the window's column" failed before 347ba60 (the window jumped past its right neighbour). "A late title leaves a native fullscreen window where it is" failed before d3f0a99 (the window ended on two workspaces). "A late rule without a destination keeps the window on its workspace" and "A late title never moves a floating window the user moved" failed before d37b5dd.
  • DaemonDesktopSynchronizationTests.lateTitleRuleMovesTheWindowAsAnOpeningWould: the daemon state after a late title equals the state after an opening with that title, for a workspace rule, a floating rule on a second display, and a window already floating when the rule moves it. The second case failed before 78ff5c7 (frame left on the first display), the third before 347ba60 (frame rebased twice, ending at the display's edge).
  • make verify passes.
  • Scenarios in the defi-e2e VM with the installed daemon, origin/main (0f24f11) against this branch, recorded through defi subscribe --json and on-screen window bounds. The scenarios used a local fixture variant that sets its title on demand; it is not part of this PR.
Scenario main Branch
Title 0.3-0.9 s after opening, workspace = "web" stays in the strip moves to web; a focused window takes the view with it, an unfocused one moves quietly
Title 0.5-1.1 s after opening, force-floating + monitor on a second display stays tiled on display 1 floating on display 2, frame on display 2
Title that matches no rule unchanged unchanged
Title 2 s after opening, or 1 s after discovery unchanged unchanged
toggle-floating before the title unchanged unchanged, the rule is skipped
Title present at opening on web on web

Exactly one defi-daemon remained after each scenario.

Regression pass, origin/main (f12ed79) against this branch rebased on it, same scenarios on both:

  • With only app_id rules (workspace, follow_focus): browser-like title changes (New Tab, then two page titles), a window closed 0.3-0.8 s after opening, two windows opening 0.1 s apart, toggle-floating before a late title, and a rename. The event sequences match; the only differences are whether discovery saw the window before its first title or its close, which varies between runs on both builds.
  • With title rules added that these windows do not match: the same.
  • DesktopE2ETests in the VM: 16 tests, 0 failures on one display; both two-display tests pass with the virtual display.

Surfaces

  • Settings: no new option.
  • CLI, IPC and event stream: unchanged. A late rule shows as window-opened then window-moved.
  • Restart: candidates are not persisted, because a restart is longer than the grace.
  • Displays: one and two displays ran in the VM. Display disconnection during the grace was not exercised.

Not covered

  • Behaviour change for existing configs: a rule that matches only the first title, such as title_regex = "^Loading" with force-floating, is reverted when the title changes within the grace (seen in the VM: the window is tiled again). After the grace it stays.

  • A window that Defi does not manage without a title rule (for example a system dialog with force-floating) is still decided at discovery.

  • The window is visible in the strip until its title arrives (about 100-400 ms in the VM), then moves. Preventing that would mean delaying placement of untitled windows.

  • A window floated late keeps the tile size it had in the meantime.

  • Any change to the window's column in the meantime, including a neighbour opening to its left, cancels the late rule. So does an app moving its own floating window during that second.

Proved by tests that fail without each fix, make verify, and real-daemon scenarios in the defi-e2e VM compared against main.

Reviewed with review-loop: 2 rounds with Claude Opus 5.5. Verdict: approved

Note

Apply window title rules once when the title arrives just after opening

  • Windows that open without a title (or with a placeholder title) now get one rule reapplication when the real title arrives within 1 second (lateTitleRuleGrace), in WindowReconciliation.swift
  • The reapplication runs only if the placement-related rule decision changed and the window has not moved; it rediscovers the window with the new decision and preserves its column position when staying on the same workspace
  • RuntimeState tracks candidates (opening title, rule decision, recorded placement, expiry) across reconciliation calls, and desktop synchronization feeds relocated windows into the normal post-reconciliation bookkeeping without the forced open-focus logic
  • Candidates are dropped if the window is gone, the 1-second grace expires, it went natively fullscreen, the title is unchanged, or the user moved the window; focus and initial-width-cycle settings are excluded from the decision comparison
  • Behavioral Change: later title changes (e.g. browser tab switches) never move a window, and a rule-relocated floating window is excluded from the floating-window monitor-change pass in Daemon.synchronizeDesktop

Macroscope summarized d37b5dd.

@github-actions github-actions Bot added the macroscope-review Request a Macroscope review label Nov 11, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Nov 11, 2026
@macroscopeapp

macroscopeapp Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Approvability

Verdict: Approved at d37b5dd

Macroscope's review found this PR approvable — Title-based placement rules now get one bounded retry shortly after a window opens, with safeguards for intervening user placement changes. The outstanding floating-window correctness finding remains subject to the separate blocking gate.

Notes:

  • This verdict was updated automatically after the outstanding correctness findings were resolved. Macroscope did not re-review the code.

You can add or adjust custom eligibility rules. Learn more.

@qeude
qeude force-pushed the fix/rules-late-title branch from fb26fb8 to f3a225a Compare November 11, 2026 17:26
@macroscopeapp
macroscopeapp Bot dismissed their stale review November 11, 2026 17:26

Dismissing prior approval to re-evaluate f3a225a

A window discovered untitled is placed again, once, on its first new
title within a second of opening, unless the user moved, resized, or
floated it first. The window keeps the focus it already has.
A late title rule re-placed the window in the runtime, but the daemon only
ran its opening steps for new windows, so a window made floating on another
display kept its frame on the first one. Late-ruled windows now go through the
same placement step, without the opening focus rules: their focus was settled
when they opened.

The test fixture takes a 'title <text>' line on stdin instead of a 'late'
argument, so a scenario can set the title at any time.
Moving a column left or right leaves its Column value unchanged, so the late
title check missed it and could send the window back to the rule's workspace.
The recorded placement now includes the column's index.
@qeude
qeude force-pushed the fix/rules-late-title branch from 5057993 to 1cf2061 Compare November 11, 2026 17:52
A late rule that kept a focused window on its workspace reinserted it after
its right neighbour; the column now goes back to its index. A window already
floating when a late monitor rule moved it was rebased twice and landed at the
display's edge; the rule rebase now owns it. The fixture's title command, used
by no test, is dropped.
Native fullscreen saves the window's tiled placement and restores it on exit.
A late rule applied meanwhile left the window on two workspaces.
A late rule without a workspace or display put the window on the monitor's
active workspace, so switching workspaces during the grace carried it along;
it now stays on its own workspace. A floating window's frame is now part of
the recorded placement, so moving or resizing it cancels the late rule.
@qeude
qeude merged commit 2ddd4ba into main Nov 11, 2026
4 checks passed
@qeude
qeude deleted the fix/rules-late-title branch November 11, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Request a Macroscope review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant