Commits

Commits on May 3, 2026

  1. Stop tracking CLAUDE.md and planning files
    Move CLAUDE.md and AGENTS.md to local-only scratch, matching the parent
    tarmac repo's convention. The file remains on disk for the working
    copy but is no longer in version control.
    mfwolffe committed
  2. Drop dead SLS-overlay code, quiet debug spam, bump to v0.4.0
    The SLS-overlay path (probe_cg_window_info, draw_border, create_overlay,
    display_scale_for_bounds, SCALE_EPSILON, set_alpha, recreate, remove_all)
    became dead when the NSWindow refactor took over screenshot exclusion.
    Drop it.
    
    Logging cleanup:
    - Default filter goes from ers=debug to ers=info; debug still available
      with RUST_LOG=ers=debug.
    - Drop the /tmp/ers-debug.log file sink left over from the
      research-branch diagnostics.
    - focus-retry no longer logs every poll; it only logs once when the
      retry finally succeeds.
    - set_bounds drops the per-call diagnostic log; only warns now if the
      NSWindow rejects the requested placement (placed_correctly=false).
    mfwolffe committed

Commits on May 2, 2026

  1. Periodically re-apply CAShapeLayer geometry to recover from sleep/wake
    NSWindow.frame survives display sleep/wake correctly (placed_correctly
    logs always returned true), but the CAShapeLayer's frame/path can be
    reset to a small rect at the layer origin during the wake transition.
    Because SLS window bounds didn't change, sync_overlay never re-applied
    state — leaving a tiny border in the bottom-left corner of an
    otherwise-correctly-positioned window.
    
    Add OverlayWindow::reapply_layer (cheap — only the layer, not the
    NSWindow) and call it once a second from the periodic reconcile, plus
    on every CGDisplayReconfiguration hotplug.
    mfwolffe committed

Commits on May 1, 2026

  1. Log NSWindow frame read-back after setFrame
    If macOS doesn't accept the requested frame (e.g. cross-screen moves
    AppKit silently rejects), set_bounds now shows the actual post-write
    frame and a placed_correctly flag — separates 'we computed the wrong
    cocoa Y' from 'we computed the right one but NSWindow refused it'.
    mfwolffe committed
  2. Re-log layout and force-resync overlays on display hotplug
    Register CGDisplayRegisterReconfigurationCallback so a monitor plug
    or resolution change re-fetches every overlay's bounds and re-applies
    set_bounds. Without this, overlays whose CG bounds didn't change but
    whose cocoa frame depends on the new primary screen height stayed at
    their pre-hotplug positions until the next SLS Move event.
    mfwolffe committed
  3. Use CGDisplayBounds(CGMainDisplayID) for primary height
    NSScreen.screens caches and only refreshes on certain notifications, so
    plugging an external monitor while ers is running left primary_height
    stuck at the pre-hotplug value. Every CG-to-Cocoa Y conversion was
    therefore off by the difference between the old primary height and the
    new one — visible as borders drawn far from their windows on the
    external display.
    
    CGDisplayBounds(CGMainDisplayID()) reflects the current state on every
    call and matches what tarmac's display module already uses.
    mfwolffe committed
  4. Add primary-screen detection and diagnostic logging
    Find the primary screen by Cocoa origin (0,0) instead of relying on
    NSScreen.screens[0], which Apple no longer guarantees to be the primary
    on every macOS version. Log the screen layout at startup and the
    CG-to-Cocoa transform on every set_bounds so we can diagnose offset
    border reports.
    mfwolffe committed

Commits on April 30, 2026

  1. Don't re-order hidden overlays on geometry sync
    orderWindow:relativeTo: re-shows an off-screen window as a side effect.
    sync_overlay called order_above whenever a target's bounds changed, which
    defeated active_only mode for stacked windows: tarmac's stack peek-out
    positions shift on every stack cycle, so each non-focused stack overlay
    popped back onto the screen as a grey border.
    mfwolffe committed

Commits on April 29, 2026

  1. Fix close-persistence and new-window race
    Two bugs from user testing:
    
    1. Spawning a new terminal made the border disappear until the next
       focus move. Cause: focus moves to the new wid before its SLS state
       passes the add_fresh filter (SLSWindowIsOrderedIn race). The
       focus-changed branch attempted add_fresh once and gave up; the
       focus-unchanged branch returned early, never retrying.
    
       Fix: in update_focus, when front == focused_wid AND the focused
       wid still has no overlay, retry add_fresh on every poll. The
       filter passes once the new window settles (typically 1-2 ticks).
    
    2. Closing the only window on a workspace left a phantom border.
       Cause: sync_overlay returned early on SLSGetWindowBounds failure
       (which is what happens for a destroyed wid) without removing the
       overlay. The Drop never fired, so NSWindow.orderOut never ran.
    
       Fix: on SLSGetWindowBounds failure, treat the window as gone and
       call remove() to drop the overlay.
    
    Also: timer's idle branch now runs reconcile_tracked once per second
    as a safety net for any other class of missed Close/Destroy event.
    mfwolffe committed
  2. Create overlays on-demand when focus lands on untracked window
    tarmac multiplexes workspaces by hiding non-current windows; only
    windows on the active workspace get enumerated by discover_windows
    at startup. When the user switched to a workspace whose windows
    weren't tracked, focus would move to a wid with no overlay and the
    border would disappear (active-only mode hides the previous overlay
    but has nothing to unhide for the new focus).
    
    Trace from /tmp/ers-debug.log:
      [focus] 130 -> 139 (tracked targets: [796, 130, 681, 795, 778])
      [hide] target=130
      (no unhide — 139 not in tracked set, border vanishes)
    
    Fix: in update_focus, if the new front isn't tracked, add_fresh it
    on the spot. Same treatment for Event::Unhide so workspace-switch
    unhide notifications can pick up newly-visible windows.
    
    Also: CFRunLoopTimerCreate flags arg was u32, should be u64
    (CFOptionFlags = unsigned long on 64-bit Darwin) — corrupted ABI for
    subsequent args and the timer was firing unreliably as a result.
    mfwolffe committed

Commits on April 28, 2026

  1. Drop CanJoinAllSpaces; log focus and hide/unhide calls
    Overlays were leaking onto every macOS space simultaneously due to
    CanJoinAllSpaces, which presents as 'border stuck on the previous
    workspace' when the user navigates between tarmac workspaces. Removing
    that flag confines each overlay to the space where it was created.
    
    Add diagnostic logging in update_focus / hide / unhide so a workspace-
    switch trace can be read with RUST_LOG=ers=debug.
    mfwolffe committed
  2. Fix border thickness; orderOut before close in Drop
    CAShapeLayer strokes are centered on the path, so to render a visible
    border_width-thick ring inside the layer bounds we want
    line_width = border_width and path inset = border_width/2 (not the
    doubled values I had — those produced 2x-thick borders user reported).
    mfwolffe committed
  3. Replace SLS overlays with NSWindow + sharingType=.none
    BorderMap now stores nswindow_overlay::OverlayWindow per target. The
    event-processing loop moves from a background thread to a CFRunLoopTimer
    on the main thread, since AppKit calls (NSWindow, CALayer) must originate
    from the main thread.
    
    Verified empirically: screencapture -l <ers_wid> returns 'could not
    create image from window' for all 5 overlays — Tahoe's screencaptureui
    honors NSWindow.sharingType where it ignores SLS sharing-state for
    SLS-only windows. Full-screen screencapture cleanly captures app
    contents without the borders blocking them.
    mfwolffe committed
  4. mfwolffe committed
  5. Research: SLS-side screenshot exclusion attempts on Tahoe
    Empirically disproved approaches for excluding ers overlays from
    Tahoe's cmd+shift+4 + space picker. All of these apply *correctly*
    to the overlay window — they just don't influence the picker.
    
    Verified via post-create probes:
    - SLSNewWindowWithOpaqueShapeAndContext baking tag bits (1<<1)|(1<<9)
      at creation succeeds; readback shows tags=0x202.
    - SLSSetWindowSharingState(0) succeeds; SLSGetWindowSharingState
      reads back state=0.
    - CGWindowListCopyWindowInfo for the overlay reports
      kCGWindowSharingState=0 (the SLS-side state propagates through to
      the CG window list, which SCWindow filters on).
    - kCGWindowIsOnscreen is absent from the dictionary entry (CG
      doesn't know the on-screen state of an SLS-only window without
      an NSWindow wrapper).
    
    Despite all of the above, the screenshot picker still hit-tests onto
    the overlay and captures the border. This means Tahoe's picker uses
    a query path that bypasses standard CGWindowSharingState and tag-bit
    filtering for SLS-only windows.
    
    Approaches still untested but likely dead-ends without docs:
    SLSSetWindowType, SLSSetWindowFiltering, SLSSetWindowSubLevel.
    
    Probe helper probe_cg_window_info added for ongoing investigation.
    Branch is for research; main remains the working EventShape build.
    mfwolffe committed
  6. Revert tag-based exclusion; restore EventShape click-through
    Setting any SLS window tags (bit 1 or bits 1|9) post-creation breaks
    border visibility on Tahoe in our recreate-overlay-on-move lifecycle:
    borders flicker on and disappear during the rapid sync_overlay churn
    that tiling produces. JankyBorders works with tags 1|9 because it
    creates each border window once and moves it via
    SLSTransactionMoveWindowWithGroup; ers releases and recreates the SLS
    window on every geometry change, and Tahoe's compositor handles the
    tagged-then-released window very differently.
    
    Restoring SLSSetWindowEventShape(empty) + SLSSetWindowEventMask(0) for
    click-through; screenshots will again include the overlay until the
    lifecycle is refactored to match JankyBorders'.
    mfwolffe committed
  7. Exclude overlays from screenshots via SLS tag bit 9
    Sets tag bits (1<<1) | (1<<9) on every overlay window: bit 1 is
    kCGSIgnoreForEvents (click-through), bit 9 hides the window from
    ScreenCaptureKit, the screenshot picker, and screen recordings.
    Mirrors JankyBorders' approach (.refs/JankyBorders/src/border.c:290
    and .refs/JankyBorders/src/misc/window.h:266).
    
    Tags are now set BEFORE SLWindowContextCreate / drawing, which is the
    critical difference from the prior tag-based attempt that poisoned
    SLSNewWindow on stack-cycle recreates. Removes the now-redundant
    SLSSetWindowEventShape/Mask, SLSSetWindowSharingState, and
    SLSSetWindowClientPerceivedType bindings.
    mfwolffe committed
  8. Revert SLSTransactionSetWindowBoundsPath donut: segfaults on Tahoe
    The guessed FFI signature for SLSTransactionSetWindowBoundsPath does
    not match Tahoe's actual ABI — calling it crashes ers with SIGSEGV
    during overlay creation, which leaves tarmac with no border renderer.
    Removing the donut path; capture-exclusion now relies solely on the
    SharingState/ClientPerceivedType advisories. Screenshots will once
    again include the overlay border, but borders themselves come back.
    mfwolffe committed

Commits on April 27, 2026

  1. Refresh overlay bounds on focus change before hide/unhide
    AX-driven window moves during stack cycles often don't emit SLS
    WINDOW_MOVE notifications, so the stored overlay frame can be stale
    when update_focus runs. Calling sync_overlay against the live
    SLSGetWindowBounds for both old and new focused windows before the
    hide/unhide pair keeps the active border on the right window during
    stack-next/prev navigation.
    mfwolffe committed
  2. Exclude overlays from screen capture via donut bounds path
    Replaces the overlay's bounds region with a CGPath containing an outer
    rect and an inner cutout. SLS evaluates the path with even-odd winding,
    so the interior is treated as outside the window: screenshots taken
    inside the bordered area capture the underlying app window instead of
    the overlay. Pairs with SharingState/ClientPerceivedType advisories for
    capture clients that honor them.
    mfwolffe committed
  3. Switch overlay click-through from SLSSetWindowTags to SLSSetWindowEventShape
    The kCGSIgnoreForEvents tag bit poisons the shared SLS connection's
    SLSNewWindow path during stack-cycle recreates, dropping border
    overlays on the inactive stack windows. An empty event shape achieves
    the same click-through without mutating tags.
    mfwolffe committed