Public

Adding *Closest Corner* resizing mode to Moves

The most requested feature.

Stats
Prompts
24
Tool calls
250
Messages
766
Assets
9
Meta
Author
@mikker
Created At
February 05, 2026
Source
Codex
Share
Permalink
Thought 0 times. Used 0 tool calls. +0 -0 [Expand]
System prompt
System prompt

AGENTS.md instructions for ~/dev/Moves

  • We want the simplest change possible. We don't care about migration. Code readability matters most, and we're happy to make bigger changes to achieve it.
  • If I pass you URLs, fetch them directly. You may also use the web command line tool (~/.dotfiles/bin/web) for a text-only version.
  • mise is installed and generally used to manage language versions. Don't force it everywhere but notice it when it's there.
  • ast-grep and ripgrep are available.
  • If I ask you to commit, you decide the message. Use format "[Imperative] [description] [optional: two newlines and an extended description"
    • Examples: "Add pricing page", "Fix whatever", "Change the universe"
  • Whenever you're done working, as the very last thing before returning to the user, run ~/.dotfiles/bin/bootledoot to notify.

Skills

A skill is a set of local instructions to follow that is stored in a SKILL.md file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.

Available skills

  • agent-browser: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages. (file: ~/.codex/skills/agent-browser/SKILL.md)
  • agent-native-architecture: This skill should be used when building AI agents using prompt-native architecture where features are defined in prompts, not code. Use it when creating autonomous agents, designing MCP servers, implementing self-modifying systems, or adopting the "trust the agent's intelligence" philosophy. (file: ~/.codex/skills/agent-native-architecture/SKILL.md)
  • andrew-kane-gem-writer: This skill should be used when writing Ruby gems following Andrew Kane's proven patterns and philosophy. It applies when creating new Ruby gems, refactoring existing gems, designing gem APIs, or when clean, minimal, production-ready Ruby library code is needed. Triggers on requests like "create a gem", "write a Ruby library", "design a gem API", or mentions of Andrew Kane's style. (file: ~/.codex/skills/andrew-kane-gem-writer/SKILL.md)
  • ask-questions-if-underspecified: Clarify requirements before implementing. Do not use automatically, only when invoked explicitly. (file: ~/.codex/skills/ask-questions-if-underspecified/SKILL.md)
  • beads: Tracks complex, multi-session work using the Beads issue tracker and dependency graphs, and provides persistent memory that survives conversation compaction. Use when work spans multiple sessions, has complex dependencies, or needs persistent context across compaction cycles. Trigger with phrases like "create task for", "what's ready to work on", "show task", "track this work", "what's blocking", or "update status". (file: ~/.codex/skills/beads/SKILL.md)
  • beautiful-mermaid: Render Mermaid diagrams as SVG and PNG using the Beautiful Mermaid library. Use when the user asks to render a Mermaid diagram. (file: ~/.agents/skills/beautiful-mermaid/SKILL.md)
  • coding-tutor: Personalized coding tutorials that build on your existing knowledge and use your actual codebase for examples. Creates a persistent learning trail that compounds over time using the power of AI, spaced repetition and quizes. (file: ~/.codex/skills/coding-tutor/SKILL.md)
  • dex: Manage tasks via dex CLI. Use when breaking down complex work, tracking implementation items, or persisting context across sessions. (file: ~/.agents/skills/dex/SKILL.md)
  • dex-plan: Create dex task from markdown planning documents (plans, specs, design docs, roadmaps) (file: ~/.agents/skills/dex-plan/SKILL.md)
  • dhh-rails-style: This skill should be used when writing Ruby and Rails code in DHH's distinctive 37signals style. It applies when writing Ruby code, Rails applications, creating models, controllers, or any Ruby file. Triggers on Ruby/Rails code generation, refactoring requests, code review, or when the user mentions DHH, 37signals, Basecamp, HEY, or Campfire style. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and the "clarity over cleverness" philosophy. (file: ~/.codex/skills/dhh-rails-style/SKILL.md)
  • dhh-ruby-style: This skill should be used when writing Ruby and Rails code in DHH's distinctive 37signals style. It applies when writing Ruby code, Rails applications, creating models, controllers, or any Ruby file. Triggers on Ruby/Rails code generation, refactoring requests, code review, or when the user mentions DHH, 37signals, Basecamp, HEY, or Campfire style. Embodies REST purity, fat models, thin controllers, Current attributes, Hotwire patterns, and the "clarity over cleverness" philosophy. (file: ~/.codex/skills/dhh-ruby-style/SKILL.md)
  • diagram-first: Diagram-first explanations using ASCII visualizations (sequence diagrams, flowcharts, component maps) to explain code, systems, request/response flows, pipelines, concurrency, and comparisons. Use when the user asks to "draw an ASCII diagram", "show the flow/logic", "visualize the architecture", "explain how it works", or when a diagram would reduce ambiguity in a technical explanation. (file: ~/.codex/skills/diagram-first/SKILL.md)
  • file-todos: This skill should be used when managing the file-based todo tracking system in the todos/ directory. It provides workflows for creating todos, managing status and dependencies, conducting triage, and integrating with slash commands and code review processes. (file: ~/.codex/skills/file-todos/SKILL.md)
  • find-skills: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill. (file: ~/.agents/skills/find-skills/SKILL.md)
  • gemini-imagegen: This skill should be used when generating and editing images using the Gemini API (Nano Banana Pro). It applies when creating images from text prompts, editing existing images, applying style transfers, generating logos with text, creating stickers, product mockups, or any image generation/manipulation task. Supports text-to-image, image editing, multi-turn refinement, and composition from multiple reference images. (file: ~/.codex/skills/gemini-imagegen/SKILL.md)
  • git-worktree: This skill manages Git worktrees for isolated parallel development. It handles creating, listing, switching, and cleaning up worktrees with a simple interactive interface, following KISS principles. (file: ~/.codex/skills/git-worktree/SKILL.md)
  • nitro-kit-components: Build or refactor Nitro Kit-style UI components, helpers, and Stimulus behaviors in Rails apps. Use when working in the Nitro Kit repo or when creating app-specific components that should follow Nitro Kit conventions (Phlex + Tailwind + minimal Stimulus), or when reviewing for Nitro Kit style guide compliance. (file: ~/.codex/skills/nitro-kit-components/SKILL.md)
  • refactor-pass: Perform a refactor pass focused on simplicity after recent changes. Use when the user asks for a refactor/cleanup pass, simplification, or dead-code removal and expects build/tests to verify behavior. (file: ~/.codex/skills/refactor-pass/SKILL.md)
  • remotion-best-practices: Best practices for Remotion - Video creation in React (file: ~/.agents/skills/remotion-best-practices/SKILL.md)
  • solidify-codebase: Deep investigation and solidification pass on an existing codebase. Use when asked to audit, simplify, or future-proof a system; perform a deep cleanup/refactor pass; identify high-impact improvement opportunities; or present a vetted change list before implementing selected items. (file: ~/.codex/skills/solidify-codebase/SKILL.md)
  • steve: Use the steve CLI to automate macOS apps via Accessibility APIs. Use when you need to drive Mac UI (apps, windows, menus, elements), run UI smoke tests, or script interactions using steve commands and JSON output. Never decide to use steve on your own. Only use when told to. (file: ~/.codex/skills/steve/SKILL.md)
  • swift-concurrency-expert: Swift Concurrency review and remediation for Swift 6.2+. Use when asked to review Swift Concurrency usage, improve concurrency compliance, or fix Swift concurrency compiler errors in a feature or file. (file: ~/.agents/skills/swift-concurrency-expert/SKILL.md)
  • swiftui-skills: Apple-authored SwiftUI and platform guidance extracted from Xcode. Helps AI agents write idiomatic, Apple-native SwiftUI with reduced hallucinations. (file: ~/.agents/skills/swiftui-skills/SKILL.md)
  • swiftui-ui-patterns: Best practices and example-driven guidance for building SwiftUI views and components. Use when creating or refactoring SwiftUI UI, designing tab architecture with TabView, composing screens, or needing component-specific patterns and examples. (file: ~/.codex/skills/swiftui-ui-patterns/SKILL.md)
  • vercel-react-best-practices: React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements. (file: ~/.codex/skills/vercel-react-best-practices/SKILL.md)
  • web-design-guidelines: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices". (file: ~/.codex/skills/web-design-guidelines/SKILL.md)
  • web-interface-guidelines: Review UI/frontend code for compliance with Vercel Web Interface Guidelines. Use when the user asks for a guideline audit, accessibility/UX compliance review, or a /web-interface-guidelines style check across files or patterns. (file: ~/.codex/skills/web-interface-guidelines/SKILL.md)
  • skill-creator: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations. (file: ~/.codex/skills/.system/skill-creator/SKILL.md)
  • skill-installer: Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos). (file: ~/.codex/skills/.system/skill-installer/SKILL.md)

How to use skills

  • Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
  • Trigger rules: If the user names a skill (with $SkillName or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
  • Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
  • How to use a skill (progressive disclosure):
    1. After deciding to use a skill, open its SKILL.md. Read only enough to follow the workflow.
    2. When SKILL.md references relative paths (e.g., scripts/foo.py), resolve them relative to the skill directory listed above first, and only consider other paths if needed.
    3. If SKILL.md points to extra folders such as references/, load only the specific files needed for the request; don't bulk-load everything.
    4. If scripts/ exist, prefer running or patching them instead of retyping large code blocks.
    5. If assets/ or templates exist, reuse them instead of recreating from scratch.
  • Coordination and sequencing:
    • If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
    • Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
  • Context hygiene:
    • Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
    • Avoid deep reference-chasing: prefer opening only files directly linked from SKILL.md unless you're blocked.
    • When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
  • Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.
System prompt

<environment_context>
~/dev/Moves
zsh
</environment_context>

System prompt

<collaboration_mode>you are now in code mode.
</collaboration_mode>

Permalink

implementing this PR
https://github.com/mikker/Moves/pull/6
Something is up with the algorithm though. We need to fix the direction as soon as the hotkey is pressed. So right now the direction, the resize direction will change while holding the hotkey, which is confusing. So my solution is that when the hotkey is pressed we calculate the resizing direction and we keep that direction as long as the hotkey is pressed. And only on a new hotkey press will it change direction. That's the one thing. Another thing is that we want to put this behavior behind a flag so there's an option to either resize always from the bottom most right corner as we did before the PR. And another mode where it resizes like the PR suggests from the corner closest to the mouse.

Permalink
Thought 13 times. Used 13 tool calls. +0 -0 [Expand]
Thinking

Preparing to open PR URL

Planning to use web.run to fetch and view the PR URL as part of following instructions; will check the repo and ensure bootledoot runs at the end.

Thinking

Confirming local repository state

Planning to inspect the local branch instead of the GitHub page, aiming to verify the commits involved by checking the repo directly with git commands and file listings.

$ ls
CLAUDE.md
LICENSE
Moves
Moves.xcodeproj
MovesTests
README.md
Updates
bin
buildServer.json
Thinking

Planning PR content inspection

I'll use a web view of the PR diff to check changes not present locally, aiming to understand the updates before proceeding.

Thinking

Switching to raw diff URL

I'm planning to fetch the diff file from the raw GitHub URL since the usual page access is failing. This will let me continue by loading the diff content directly.

Thinking

Clarifying PR diff access strategy

The user's PR URL can open the base page, but the diff link might be dynamically loaded and not directly accessible, so I plan to search the web for the PR diff using web search instead.

Thinking

Switching to local repo for resizing fix

Authentication blocks GitHub search, so I'll inspect the repo code directly with ripgrep to find the resizing algorithm and implement the necessary fix, possibly leveraging an existing branch.

$ rg -n \
Moves/Modifiers.swift:76:    let resizeMods = Defaults[.resizeModifiers]
Moves/Modifiers.swift:80:    } else if !resizeMods.isEmpty && mods == resizeMods {
Moves/Modifiers.swift:81:      return .resize
Moves/General.xib:18:                <outlet property="resizeDisabledLabel" destination="Myx-wv-MyF" id="xtN-mB-7he"/>
Moves/General.xib:19:                <outlet property="resizeModifiersControl" destination="35x-16-g3g" id="tYb-6d-GkA"/>
Moves/General.xib:29:            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
Moves/General.xib:31:                <gridView xPlacement="center" yPlacement="center" rowAlignment="none" rowSpacing="8" columnSpacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="GIn-GQ-HEx">
Moves/General.xib:58:                            <textField key="contentView" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PHb-J6-vtI">
Moves/General.xib:60:                                <textFieldCell key="cell" alignment="right" title="Hold these modifiers while moving your mouse to move or resize windows." id="o8x-4G-JsW">
Moves/General.xib:68:                            <stackView key="contentView" distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kh6-6J-rqs">
Moves/General.xib:71:                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Wea-ed-PN5">
Moves/General.xib:79:                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2H3-Uk-4jY">
Moves/General.xib:103:                            <segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sX5-eS-pz1">
Moves/General.xib:122:                            <stackView key="contentView" distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lsa-fm-SX8">
Moves/General.xib:125:                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dET-iH-8jX">
Moves/General.xib:133:                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Myx-wv-MyF">
Moves/General.xib:157:                            <segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="35x-16-g3g">
Moves/General.xib:170:                                    <action selector="resizeModifiersChanged:" target="-2" id="H5O-yw-1e0"/>
Moves/General.xib:176:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sK7-iC-9LK">
Moves/General.xib:188:                            <box key="contentView" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="eeg-Ps-8BC">
Moves/General.xib:194:                            <textField key="contentView" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6UF-4m-onb">
Moves/General.xib:204:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cGK-Ag-p9a">
Moves/General.xib:214:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EQi-31-5EG">
Moves/General.xib:227:                            <textField key="contentView" focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NcR-gT-JZa">
Moves/General.xib:237:                            <box key="contentView" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="GUW-ez-03d">
Moves/General.xib:244:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pql-Q3-TBr">
Moves/General.xib:257:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lwo-4l-kcU">
Moves/General.xib:270:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lx4-F9-Duf">
Moves/General.xib:283:                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Dzr-F9-cUR">
Moves/AccessibilityElement.swift:39:  func resizeTo(_ size: CGSize) {
Moves/WindowHandler.swift:88:    case .resize: resize(event)
Moves/WindowHandler.swift:109:  private func resize(_ event: NSEvent) {
Moves/WindowHandler.swift:128:      // Closer to top: resize from top edge
Moves/WindowHandler.swift:132:      // Closer to bottom: resize from bottom edge
Moves/WindowHandler.swift:139:      // Closer to left: resize from left edge
Moves/WindowHandler.swift:143:      // Closer to right: resize from right edge
Moves/WindowHandler.swift:148:    window.resizeTo(CGSize(width: newWidth, height: newHeight))
Moves/Defaults.swift:18:  static let resizeModifiers = Key<Set<Modifier>>(
Moves/Defaults.swift:19:    "resizeModifiers", default: Set(arrayLiteral: .option, .shift))
Moves/Intention.swift:5:  case resize
Moves/GeneralPreferencesController.swift:24:  @IBOutlet var resizeDisabledLabel: NSTextField!
Moves/GeneralPreferencesController.swift:25:  @IBOutlet var resizeModifiersControl: NSSegmentedControl!
Moves/GeneralPreferencesController.swift:59:      for await value in Defaults.updates(.resizeModifiers) {
Moves/GeneralPreferencesController.swift:62:          case .command: self.resizeModifiersControl.setSelected(true, forSegment: Segment.command)
Moves/GeneralPreferencesController.swift:63:          case .option: self.resizeModifiersControl.setSelected(true, forSegment: Segment.option)
Moves/GeneralPreferencesController.swift:64:          case .control: self.resizeModifiersControl.setSelected(true, forSegment: Segment.control)
Moves/GeneralPreferencesController.swift:65:          case .shift: self.resizeModifiersControl.setSelected(true, forSegment: Segment.shift)
Moves/GeneralPreferencesController.swift:70:        self.resizeDisabledLabel.isHidden = !value.isEmpty
Moves/GeneralPreferencesController.swift:121:  @IBAction func resizeModifiersChanged(_ sender: Any) {
Moves/GeneralPreferencesController.swift:123:    if resizeModifiersControl.isSelected(forSegment: Segment.command) { mods.insert(.command) }
Moves/GeneralPreferencesController.swift:124:    if resizeModifiersControl.isSelected(forSegment: Segment.option) { mods.insert(.option) }
Moves/GeneralPreferencesController.swift:125:    if resizeModifiersControl.isSelected(forSegment: Segment.control) { mods.insert(.control) }
Moves/GeneralPreferencesController.swift:126:    if resizeModifiersControl.isSelected(forSegment: Segment.shift) { mods.insert(.shift) }
Moves/GeneralPreferencesController.swift:127:    if resizeModifiersControl.isSelected(forSegment: Segment.fn) { mods.insert(.fn) }
Moves/GeneralPreferencesController.swift:128:    Defaults[.resizeModifiers] = mods
Moves/Excludes.xib:20:            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
Moves/Excludes.xib:22:                <segmentedControl verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9Oe-72-8Ld">
Moves/Excludes.xib:38:                <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ohi-BG-Dii">
Moves/Excludes.xib:48:                <scrollView autohidesScrollers="YES" horizontalLineScroll="38" horizontalPageScroll="10" verticalLineScroll="38" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5GZ-la-ukE">
Moves/Excludes.xib:52:                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
Moves/Excludes.xib:54:                            <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnSelection="YES" autosaveColumns="NO" rowHeight="36" rowSizeStyle="automatic" viewBased="YES" id="mCA-TU-5iS">
Moves/Excludes.xib:56:                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
Moves/Excludes.xib:71:                                        <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
Moves/Excludes.xib:75:                                                <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
Moves/Excludes.xib:77:                                                    <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="2XD-Xz-Rys">
Moves/Excludes.xib:85:                                                    <textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KMQ-1a-6F1">
Moves/Excludes.xib:121:                        <autoresizingMask key="autoresizingMask"/>
Moves/Excludes.xib:125:                        <autoresizingMask key="autoresizingMask"/>
Moves/Excludes.xib:128:                <textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="dcd-xC-Lfr">
Moves/ActiveWindow.swift:48:        // Just use position to handle both moves and resize in correct order
Moves/ActiveWindow.swift:53:        window.resizeTo(CGSize(width: screenRect.width, height: screenRect.height))
Moves/ActiveWindow.swift:61:      window.resizeTo(CGSize(width: currentSize.width, height: screenRect.height))
Moves/ActiveWindow.swift:68:      window.resizeTo(CGSize(width: screenRect.width, height: currentSize.height))
Moves/ActiveWindow.swift:83:      window.resizeTo(CGSize(width: width, height: screenRect.height))
Moves/ActiveWindow.swift:88:      window.resizeTo(CGSize(width: width, height: screenRect.height))
Moves/ActiveWindow.swift:94:      window.resizeTo(CGSize(width: width, height: height))
Moves/ActiveWindow.swift:100:      window.resizeTo(CGSize(width: width, height: height))
Moves/ActiveWindow.swift:168:    // Then resize if needed - this way we ensure the window is positioned correctly
Moves/ActiveWindow.swift:172:      window.resizeTo(size)
Thinking

Inspecting window resize handling

$ sed -n '1,220p' Moves/WindowHandler.swift
import AXSwift
import Cocoa
import Defaults

class WindowHandler {
  var monitors: [Any?] = []
  var window: AccessibilityElement?

  var intention: Intention = .idle {
    didSet { intentionChanged(self.intention) }
  }

  deinit {
    removeMonitors()
  }

  func intentionChanged(_ intention: Intention) {
    removeMonitors()

    if intention == .idle {
      self.window = nil
      return
    }

    let loc = Mouse.location()
    guard let window = AccessibilityElement.at(loc)?.window else { return }

    let app = window.application

    // App is excluded?

    if let path = applicationPath(app: app),
      Defaults[.excludedApplicationPaths].contains(path)
    {
      return
    }

    self.window = window

    if Defaults[.bringToFront] {
      try? app?.setAttribute(.frontmost, value: true)
      try? window.ref.setAttribute(.main, value: true)
    }

    self.monitors.append(
      NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved) { event in
        self.mouseMoved(event)
      }
    )
    self.monitors.append(
      NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { event in
        self.mouseMoved(event)
        return event
      }
    )
  }

  private func getBundleID(for axApplication: AXUIElement) -> String? {
    var pid: pid_t = 0
    if AXUIElementGetPid(axApplication, &pid) == .success {
      if let app = NSRunningApplication(processIdentifier: pid) {
        return app.bundleIdentifier
      }
    }
    return nil
  }

  private func applicationPath(app maybeApp: Application?) -> String? {
    guard let app = maybeApp else {
      print("no app")
      return nil
    }
    guard let bundleId: String = getBundleID(for: app.element) else {
      print("no bundle id")
      return nil
    }
    guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId) else {
      print("no url")
      return nil
    }
    let path = url.path
    return path.hasSuffix("/") ? path : path.appending("/")
  }

  private func mouseMoved(_ event: NSEvent) {
    switch intention {
    case .move: move(event)
    case .resize: resize(event)
    case .idle:
      assertionFailure("mouseMoved obseved while ignoring")
    }
  }

  private func removeMonitors() {
    monitors.forEach { (monitor) in
      guard let m = monitor else { return }
      NSEvent.removeMonitor(m)
    }
    self.monitors = []
  }

  private func move(_ event: NSEvent) {
    guard let window = self.window else { return }
    guard let pos = window.position else { return }
    let dest = CGPoint(x: pos.x + event.deltaX, y: pos.y + event.deltaY)
    window.moveTo(dest)
  }

  private func resize(_ event: NSEvent) {
    guard let window = self.window else { return }
    guard let size = window.size else { return }
    guard let pos = window.position else { return }
    let mouseY = NSEvent.mouseLocation.y
    let mouseX = NSEvent.mouseLocation.x
    let windowTop = pos.y + size.height
    let windowBottom = pos.y
    let windowLeft = pos.x
    let windowRight = pos.x + size.width
    // Determine which border is closer: top or bottom
    let distanceToTop = abs(mouseY - windowTop)
    let distanceToBottom = abs(mouseY - windowBottom)
    // Determine which border is closer: left or right
    let distanceToLeft = abs(mouseX - windowLeft)
    let distanceToRight = abs(mouseX - windowRight)
    var newHeight = size.height
    var newY = pos.y
    if distanceToTop > distanceToBottom {
      // Closer to top: resize from top edge
      newHeight = size.height + event.deltaY
      newY = pos.y
    } else {
      // Closer to bottom: resize from bottom edge
      newHeight = size.height - event.deltaY
      newY = pos.y + event.deltaY
    }
    var newWidth = size.width
    var newX = pos.x
    if distanceToLeft < distanceToRight {
      // Closer to left: resize from left edge
      newWidth = size.width - event.deltaX
      newX = pos.x + event.deltaX
    } else {
      // Closer to right: resize from right edge
      newWidth = size.width + event.deltaX
      newX = pos.x
    }
    window.moveTo(CGPoint(x: newX, y: newY))
    window.resizeTo(CGSize(width: newWidth, height: newHeight))
  }

}
Thinking

Reviewing resize direction handling

I'm checking Defaults.swift for any config or flags controlling whether resize originates from the bottom-right corner or the corner closest to the mouse, aiming to clarify how to store and apply the intended resize direction consistently on hotkey press.

$ sed -n '1,200p' Moves/Defaults.swift
import Cocoa
import Defaults

enum Modifier: String, Defaults.Serializable {
  case command = "command"
  case option = "option"
  case control = "control"
  case shift = "shift"
  case fn = "fn"
}

extension Defaults.Keys {
  static let accessibilityEnabled = Key<Bool>("accessibilityEnabled", default: false)

  static let moveModifiers = Key<Set<Modifier>>(
    "moveModifiers", default: Set(arrayLiteral: .command, .shift))

  static let resizeModifiers = Key<Set<Modifier>>(
    "resizeModifiers", default: Set(arrayLiteral: .option, .shift))

  static let bringToFront = Key<Bool>("bringToFront", default: false)

  static let showPrefsOnLaunch = Key<Bool>("showPrefsOnLaunch", default: true)
  static let showInMenubar = Key<Bool>("showInMenubar", default: true)

  static let excludedApplicationPaths = Key<Set<String>>("excludedApplicationPaths", default: [])
}
Thinking

Reviewing preferences UI for new default

I'm exploring the GeneralPreferencesController and its xib to evaluate adding a new default flag in the UI, likely under preferences.

$ sed -n '1,220p' Moves/GeneralPreferencesController.swift
import Cocoa
import Defaults
import LaunchAtLogin
import Settings
import Sparkle

enum Segment {
  static let command = 0
  static let option = 1
  static let control = 2
  static let shift = 3
  static let fn = 4
}

class GeneralPreferencesController: NSViewController, SettingsPane {
  var paneIdentifier: Settings.PaneIdentifier = .general
  var paneTitle: String = "General"

  override var nibName: NSNib.Name? { "General" }

  @IBOutlet var moveDisabledLabel: NSTextField!
  @IBOutlet var moveModifiersControl: NSSegmentedControl!

  @IBOutlet var resizeDisabledLabel: NSTextField!
  @IBOutlet var resizeModifiersControl: NSSegmentedControl!

  @IBOutlet var bringToFrontCheckbox: NSButton!

  @IBOutlet var accessibilityStatusButton: NSButton!
  @IBOutlet var accessibilityPrefsButton: NSButton!

  @IBOutlet var showPrefsOnLaunch: NSButton!
  @IBOutlet var showInMenubar: NSButton!
  @IBOutlet var launchAtLoginCheckbox: NSButton!
  @IBOutlet var checkForUpdatesCheckbox: NSButton!

  override func viewDidLoad() {
    super.viewDidLoad()

    self.preferredContentSize = NSSize(width: 400, height: 434)

    Task {
      for await value in Defaults.updates(.moveModifiers) {
        value.forEach { (mod) in
          switch mod {
          case .command: self.moveModifiersControl.setSelected(true, forSegment: Segment.command)
          case .option: self.moveModifiersControl.setSelected(true, forSegment: Segment.option)
          case .control: self.moveModifiersControl.setSelected(true, forSegment: Segment.control)
          case .shift: self.moveModifiersControl.setSelected(true, forSegment: Segment.shift)
          case .fn: self.moveModifiersControl.setSelected(true, forSegment: Segment.fn)
          }
        }

        self.moveDisabledLabel.isHidden = !value.isEmpty
      }
    }

    Task {
      for await value in Defaults.updates(.resizeModifiers) {
        value.forEach { (mod) in
          switch mod {
          case .command: self.resizeModifiersControl.setSelected(true, forSegment: Segment.command)
          case .option: self.resizeModifiersControl.setSelected(true, forSegment: Segment.option)
          case .control: self.resizeModifiersControl.setSelected(true, forSegment: Segment.control)
          case .shift: self.resizeModifiersControl.setSelected(true, forSegment: Segment.shift)
          case .fn: self.moveModifiersControl.setSelected(true, forSegment: Segment.fn)
          }
        }

        self.resizeDisabledLabel.isHidden = !value.isEmpty
      }
    }

    Task {
      for await value in Defaults.updates(.bringToFront) {
        self.bringToFrontCheckbox.state = value ? .on : .off
      }
    }

    Task {
      for await value in Defaults.updates(.accessibilityEnabled) {
        if value {
          self.accessibilityStatusButton.image = NSImage(named: NSImage.statusAvailableName)
          self.accessibilityStatusButton.title = "Enabled"
        } else {
          self.accessibilityStatusButton.image = NSImage(
            named: NSImage.statusUnavailableName)
          self.accessibilityStatusButton.title = "Not enabled"
        }
      }
    }

    Task {
      for await value in Defaults.updates(.showPrefsOnLaunch) {
        self.showPrefsOnLaunch.state = value ? .on : .off
      }
    }

    Task {
      for await value in Defaults.updates(.showInMenubar) {
        self.showInMenubar.state = value ? .on : .off
      }
    }

    launchAtLoginCheckbox.state = LaunchAtLogin.isEnabled ? .on : .off
    if let updater = (NSApplication.shared.delegate as? AppDelegate)?.sparkle.updater {
      checkForUpdatesCheckbox.state = updater.automaticallyChecksForUpdates ? .on : .off
    }
  }

  @IBAction func moveModifiersChanged(_ sender: Any) {
    var mods: Set<Modifier> = Set()
    if moveModifiersControl.isSelected(forSegment: Segment.command) { mods.insert(.command) }
    if moveModifiersControl.isSelected(forSegment: Segment.option) { mods.insert(.option) }
    if moveModifiersControl.isSelected(forSegment: Segment.control) { mods.insert(.control) }
    if moveModifiersControl.isSelected(forSegment: Segment.shift) { mods.insert(.shift) }
    if moveModifiersControl.isSelected(forSegment: Segment.fn) { mods.insert(.fn) }
    Defaults[.moveModifiers] = mods
  }

  @IBAction func resizeModifiersChanged(_ sender: Any) {
    var mods: Set<Modifier> = Set()
    if resizeModifiersControl.isSelected(forSegment: Segment.command) { mods.insert(.command) }
    if resizeModifiersControl.isSelected(forSegment: Segment.option) { mods.insert(.option) }
    if resizeModifiersControl.isSelected(forSegment: Segment.control) { mods.insert(.control) }
    if resizeModifiersControl.isSelected(forSegment: Segment.shift) { mods.insert(.shift) }
    if resizeModifiersControl.isSelected(forSegment: Segment.fn) { mods.insert(.fn) }
    Defaults[.resizeModifiers] = mods
  }

  @IBAction func activateOnHandleClicked(_ sender: Any) {
    Defaults[.bringToFront] = bringToFrontCheckbox.state == .on
  }

  @IBAction func showPrefsOnLaunchClicked(_ sender: Any) {
    let state = self.showPrefsOnLaunch.state == .on
    Defaults[.showPrefsOnLaunch] = state
  }

  @IBAction func showInMenubarClicked(_ sender: Any) {
    let state = self.showInMenubar.state == .on

    if !state && Defaults[.showInMenubar] {
      if let window = (NSApp.delegate as? AppDelegate)?.preferencesWindowController.window {
        let alert = NSAlert()
        alert.messageText = "If you need to see this window again, launch the Moves app twice."
        alert.beginSheetModal(for: window, completionHandler: nil)
      }
    }

    Defaults[.showInMenubar] = state
  }

  @IBAction func launchAtLoginCheckboxClicked(_ sender: Any) {
    let state = self.launchAtLoginCheckbox.state == .on
    LaunchAtLogin.isEnabled = state
  }

  @IBAction func checkForUpdatesCheckboxClicked(_ sender: Any) {
    let state = self.launchAtLoginCheckbox.state == .on
    if let updater = (NSApplication.shared.delegate as? AppDelegate)?.sparkle.updater {
      updater.automaticallyChecksForUpdates = state
    }
  }

  @IBAction func accessibilityPrefsButtonClicked(_ sender: Any) {
    if let url = URL(
      string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")
    {
      NSWorkspace.shared.open(url)
    }
  }
}
Thinking

Noticing existing bug and planning UI additions

I found an existing bug where the resize modifiers update incorrectly sets moveModifiersControl but will not fix it now. Instead, I'll focus on adding a UI control for the resize corner behavior in General.xib to allow users to set a flag for it.

$ rg -n \
18:                <outlet property="resizeDisabledLabel" destination="Myx-wv-MyF" id="xtN-mB-7he"/>
19:                <outlet property="resizeModifiersControl" destination="35x-16-g3g" id="tYb-6d-GkA"/>
60:                                <textFieldCell key="cell" alignment="right" title="Hold these modifiers while moving your mouse to move or resize windows." id="o8x-4G-JsW">
170:                                    <action selector="resizeModifiersChanged:" target="-2" id="H5O-yw-1e0"/>
Thinking

Opening controls section

$ sed -n '120,240p' Moves/General.xib
                        <gridCell row="yQn-m2-QMD" column="139-e0-eDr" headOfMergedCell="7FQ-jY-oG5" id="OQa-68-plS"/>
                        <gridCell row="yQn-m2-QMD" column="kUZ-Sh-B2b" id="i17-iW-rZ7">
                            <stackView key="contentView" distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lsa-fm-SX8">
                                <rect key="frame" x="144" y="312" width="107" height="16"/>
                                <subviews>
                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dET-iH-8jX">
                                        <rect key="frame" x="-2" y="0.0" width="47" height="16"/>
                                        <textFieldCell key="cell" lineBreakMode="clipping" title="Resize" id="pe9-3a-Svk">
                                            <font key="font" metaFont="systemBold"/>
                                            <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                            <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                        </textFieldCell>
                                    </textField>
                                    <textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Myx-wv-MyF">
                                        <rect key="frame" x="49" y="0.0" width="60" height="16"/>
                                        <textFieldCell key="cell" lineBreakMode="clipping" title="Disabled" id="uwH-WJ-O0e">
                                            <font key="font" metaFont="systemSemibold" size="13"/>
                                            <color key="textColor" name="systemOrangeColor" catalog="System" colorSpace="catalog"/>
                                            <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                            <allowedInputSourceLocales>
                                                <string>NSAllRomanInputSourcesLocaleIdentifier</string>
                                            </allowedInputSourceLocales>
                                        </textFieldCell>
                                    </textField>
                                </subviews>
                                <visibilityPriorities>
                                    <integer value="1000"/>
                                    <integer value="1000"/>
                                </visibilityPriorities>
                                <customSpacing>
                                    <real value="3.4028234663852886e+38"/>
                                    <real value="3.4028234663852886e+38"/>
                                </customSpacing>
                            </stackView>
                        </gridCell>
                        <gridCell row="dcL-qB-dxg" column="139-e0-eDr" id="UCW-28-vUx"/>
                        <gridCell row="dcL-qB-dxg" column="kUZ-Sh-B2b" id="sDI-mY-4y4">
                            <segmentedControl key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="35x-16-g3g">
                                <rect key="frame" x="141" y="282" width="125" height="24"/>
                                <segmentedCell key="cell" borderStyle="border" alignment="left" segmentDistribution="fillEqually" style="rounded" trackingMode="selectAny" id="pZK-aT-Ouh">
                                    <font key="font" metaFont="system"/>
                                    <segments>
                                        <segment label="⌘" toolTip="Command"/>
                                        <segment label="⌥" toolTip="Option" tag="1"/>
                                        <segment label="⌃" toolTip="Control"/>
                                        <segment label="⇧" toolTip="Shift"/>
                                        <segment label="fn"/>
                                    </segments>
                                </segmentedCell>
                                <connections>
                                    <action selector="resizeModifiersChanged:" target="-2" id="H5O-yw-1e0"/>
                                </connections>
                            </segmentedControl>
                        </gridCell>
                        <gridCell row="yqj-bD-Wm6" column="139-e0-eDr" id="Hcf-RK-JeH"/>
                        <gridCell row="yqj-bD-Wm6" column="kUZ-Sh-B2b" id="orK-mM-Xc9">
                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sK7-iC-9LK">
                                <rect key="frame" x="142" y="251" width="156" height="18"/>
                                <buttonCell key="cell" type="check" title="Bring window to front" bezelStyle="regularSquare" imagePosition="left" inset="2" id="QzD-q8-ZMJ">
                                    <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
                                    <font key="font" metaFont="system"/>
                                </buttonCell>
                                <connections>
                                    <action selector="activateOnHandleClicked:" target="-2" id="u9l-pk-xNo"/>
                                </connections>
                            </button>
                        </gridCell>
                        <gridCell row="bXk-Eg-Dpj" column="139-e0-eDr" headOfMergedCell="1ni-o9-SqW" xPlacement="fill" id="1ni-o9-SqW">
                            <box key="contentView" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="eeg-Ps-8BC">
                                <rect key="frame" x="0.0" y="229" width="360" height="5"/>
                            </box>
                        </gridCell>
                        <gridCell row="bXk-Eg-Dpj" column="kUZ-Sh-B2b" headOfMergedCell="1ni-o9-SqW" id="KMa-Je-XAr"/>
                        <gridCell row="jSN-rX-mvI" column="139-e0-eDr" id="Q0T-Cz-3XW">
                            <textField key="contentView" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6UF-4m-onb">
                                <rect key="frame" x="52" y="195" width="80" height="16"/>
                                <textFieldCell key="cell" lineBreakMode="clipping" title="Accessibility" id="dKF-2t-RO5">
                                    <font key="font" usesAppearanceFont="YES"/>
                                    <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
                                    <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                </textFieldCell>
                            </textField>
                        </gridCell>
                        <gridCell row="jSN-rX-mvI" column="kUZ-Sh-B2b" id="Thv-19-gQY">
                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cGK-Ag-p9a">
                                <rect key="frame" x="144" y="195" width="71" height="16"/>
                                <buttonCell key="cell" type="bevel" title="Enabled" bezelStyle="rounded" image="NSStatusNone" imagePosition="leading" alignment="center" imageScaling="proportionallyDown" inset="2" id="tnn-Ta-z0I">
                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                    <font key="font" metaFont="system"/>
                                </buttonCell>
                            </button>
                        </gridCell>
                        <gridCell row="Q69-bN-vYw" column="139-e0-eDr" id="xcd-tp-YHE"/>
                        <gridCell row="Q69-bN-vYw" column="kUZ-Sh-B2b" id="o1v-Ic-YiG">
                            <button key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="EQi-31-5EG">
                                <rect key="frame" x="137" y="160" width="223" height="32"/>
                                <buttonCell key="cell" type="push" title="Open Accessibility Preferences" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="z1f-Z2-JP3">
                                    <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
                                    <font key="font" metaFont="system"/>
                                </buttonCell>
                                <connections>
                                    <action selector="accessibilityPrefsButtonClicked:" target="-2" id="mRZ-7j-NDA"/>
                                </connections>
                            </button>
                        </gridCell>
                        <gridCell row="aJ8-08-jgq" column="139-e0-eDr" id="Dsu-hS-JtD"/>
                        <gridCell row="aJ8-08-jgq" column="kUZ-Sh-B2b" id="lv2-6g-kfu">
                            <textField key="contentView" focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NcR-gT-JZa">
                                <rect key="frame" x="142" y="129" width="220" height="30"/>
                                <textFieldCell key="cell" selectable="YES" alignment="left" title="Moves needs special permissions to work for other apps than itself." id="Jbg-Jb-oyc">
                                    <font key="font" metaFont="cellTitle"/>
                                    <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
                                    <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
                                </textFieldCell>
                            </textField>
                        </gridCell>
                        <gridCell row="jNT-nY-o2L" column="139-e0-eDr" headOfMergedCell="8Sp-Yz-gMZ" xPlacement="fill" id="8Sp-Yz-gMZ">
                            <box key="contentView" verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="GUW-ez-03d">
                                <rect key="frame" x="0.0" y="106" width="360" height="5"/>
                            </box>
                        </gridCell>
Thinking

Planning preference UI and flag handling

The resizing behavior needs a user preference behind a flag, likely added to existing preferences UI with a new checkbox. Next steps include updating the preferences xib and controller, and fixing the resize algorithm to determine direction once per hotkey press by inspecting modifier handling in Modifiers.swift.

$ sed -n '1,200p' Moves/Modifiers.swift
import Cocoa
import Defaults

class Modifiers {
  typealias ChangeHandler = (Intention) -> Void

  let handleChange: ChangeHandler

  var onMonitors: [Any?] = []
  var offMonitors: [Any?] = []

  var intention: Intention = .idle {
    didSet { intentionChanged(oldValue: oldValue) }
  }

  init(changeHandler: @escaping ChangeHandler) {
    self.handleChange = changeHandler
  }

  deinit {
    remove()
  }

  func observe() {
    remove()

    onMonitors.append(contentsOf: [
      NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged, handler: self.globalMonitor),
      NSEvent.addLocalMonitorForEvents(matching: .flagsChanged, handler: self.localMonitor),
    ])
  }

  func remove() {
    removeOffMonitors()
    removeOnMonitors()
  }

  private func removeOnMonitors() {
    offMonitors.forEach { (monitor) in
      guard let m = monitor else { return }
      NSEvent.removeMonitor(m)
    }

    offMonitors = []
  }

  private func removeOffMonitors() {
    offMonitors.forEach { (monitor) in
      guard let m = monitor else { return }
      NSEvent.removeMonitor(m)
    }

    offMonitors = []
  }

  private func intentionChanged(oldValue: Intention) {
    guard oldValue != intention else { return }

    //    print("intention:\(intention)")

    if intention == .idle {
      removeOffMonitors()
    } else {
      setupOffMonitors()
    }

    handleChange(intention)
  }

  private func intentionFrom(_ flags: NSEvent.ModifierFlags) -> Intention {
    let mods = modsFromFlags(flags)

    if mods.isEmpty { return .idle }

    let moveMods = Defaults[.moveModifiers]
    let resizeMods = Defaults[.resizeModifiers]

    if !moveMods.isEmpty && mods == moveMods {
      return .move
    } else if !resizeMods.isEmpty && mods == resizeMods {
      return .resize
    } else {
      return .idle
    }
  }

  private func modsFromFlags(_ flags: NSEvent.ModifierFlags) -> Set<Modifier> {
    var mods: Set<Modifier> = Set()
    if flags.contains(.command) { mods.insert(.command) }
    if flags.contains(.option) { mods.insert(.option) }
    if flags.contains(.control) { mods.insert(.control) }
    if flags.contains(.shift) { mods.insert(.shift) }
    if flags.contains(.function) { mods.insert(.fn) }
    return mods
  }

  private func setupOffMonitors() {
    offMonitors.append(contentsOf: [
      NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved, handler: self.globalMonitor),
      NSEvent.addLocalMonitorForEvents(matching: .mouseMoved, handler: self.localMonitor),
    ])
  }

  private func globalMonitor(_ event: NSEvent) {
    self.intention = self.intentionFrom(event.modifierFlags)
  }

  private func localMonitor(_ event: NSEvent) -> NSEvent? {
    globalMonitor(event)
    return event
  }
}
Thinking

Clarifying resize intent and anchor behavior

I’m defining that when the intention changes to resize, the direction should be computed from the mouse position and stored for consistent resizing until idle. I also need to support an option to revert to the old behavior, always resizing from the bottom-right corner regardless of mouse location.

Analyzing coordinate logic for bottom-right resizing

I traced how deltaX and deltaY affect width, height, and position to confirm that resizing from the bottom-right corner means anchoring the top-left fixed—width increases with deltaX and height decreases with deltaY while adjusting y to keep the top constant. This clarifies the coordinate transformations needed to replicate the old resizing behavior.

$ git log -n 20 -- Moves/WindowHandler.swift
a323a6b - (HEAD -> refs/heads/trochr/main) Enhance window resizing logic to adjust based on mouse proximity to edges (9 months ago) <trochr>
747cfbc - (tag: refs/tags/v1.7.2) Initial public (1 year, 5 months ago) <Mikkel Malmberg>
Loading more...