From 93b3564de251b6f306f436237dcf6c71a11d5837 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 12 Nov 2026 14:43:43 -0500 Subject: [PATCH 1/2] Sheets: confirm clicks that open or close a sheet, and route agents to the parent window (#243, #245) A press that opens or closes a sheet now reads back the window's sheets when the app answers cannotComplete, and reports completed with sheet opened/closed instead of unknown. desktop_windows marks a sheet's own window attached_sheet with sheet_of_window_id and a hint to inspect the parent; inspecting the sheet's window refuses at once with ATTACHED_SHEET naming the parent; the parent's screenshot draws its attached sheets; desktop_dialog reads SwiftUI alert buttons (titled by description) and names the parent when given the sheet's window. An action's follow-up observation retries once when the tree is mid-change. Adds the sheetfix fixture (beginSheet, NSAlert sheet, SwiftUI .alert). Why: native2/Sources/DesktopMac/Pointer.swift | AppKit and SwiftUI answer an AXPress that opens or closes a sheet with cannotComplete although the press took effect, so an indeterminate press is confirmed by the window's sheet count changing Why: native2/Sources/DesktopMac/AX.swift | AppKit windows don't support AXSheets: sheets are AXSheet children of the window, and a SwiftUI alert's window adds a transparent margin around the AXSheet frame, so match by containment and center, not exact bounds Why: native2/Sources/DesktopMac/Capture.swift | a sheet is its own window server window, so a capture of its parent omits it; attached sheets are drawn into the parent's image so it matches the parent's elements Co-Authored-By: Claude Opus 5.5 --- dist/tools.js | 2 +- .../Sources/DesktopCore/Availability.swift | 6 ++ native2/Sources/DesktopHost/Dialogs.swift | 6 ++ native2/Sources/DesktopHost/Inspect.swift | 22 ++++- native2/Sources/DesktopMac/AX.swift | 38 +++++++- native2/Sources/DesktopMac/Capture.swift | 27 +++++- native2/Sources/DesktopMac/Dialogs.swift | 14 ++- native2/Sources/DesktopMac/Pointer.swift | 34 ++++++- native2/Sources/DesktopMac/Windows.swift | 48 ++++++++++ .../DesktopProtocol/ContractText.swift | 2 +- native2/Tests/CoreTests/CoreTests.swift | 9 ++ native2/fixture/sheetfix.swift | 96 +++++++++++++++++++ src/tools.ts | 2 +- 13 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 native2/fixture/sheetfix.swift diff --git a/dist/tools.js b/dist/tools.js index d3200098..f6fec816 100644 --- a/dist/tools.js +++ b/dist/tools.js @@ -229,7 +229,7 @@ export function desktopTools({ place, app }) { { name: "desktop_windows", op: "windows", - description: `List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable…) and a hint naming the tool that recovers it; check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.`, + description: `List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable, attached_sheet…) and a hint naming the tool that recovers it; a sheet or alert is listed as a window of its own but is inspected, clicked and answered through the window it is attached to (sheet_of_window_id); check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.`, parameters: { type: "object", required: ["pid"], diff --git a/native2/Sources/DesktopCore/Availability.swift b/native2/Sources/DesktopCore/Availability.swift index 8cdb8a56..71614986 100644 --- a/native2/Sources/DesktopCore/Availability.swift +++ b/native2/Sources/DesktopCore/Availability.swift @@ -12,6 +12,12 @@ public func windowAvailability(_ window: JSON, sessionLocked: Bool, appHidden: B return .object([("state", "unavailable"), ("reason", "open_save_panel_service"), ("hint", "This window belongs to the service that draws another app's Open or Save panel, and to its sheets such as Go to Folder. Use the app that opened the panel: its desktop_windows lists the panel with the same title and bounds, which desktop_inspect and desktop_click reach in the background. To go to a folder or file, pass the app's target to desktop_dialog, then desktop_dialog_answer with path.")]) } + // A sheet's own window shows only pixels and takes no input; its parent's snapshot holds the sheet's elements and + // image, whether or not the window server calls the sheet on screen (#245). + if window["observation_reason"]?.string == "attached_sheet", let parent = window["sheet_of_window_id"]?.int { + return .object([("state", "unavailable"), ("reason", "attached_sheet"), ("parent_window_id", JSON(parent)), + ("hint", JSON("This window is a sheet attached to window \(parent) of the same app. Inspect window \(parent) instead: its snapshot shows the sheet and lists it as an AXSheet with its text and buttons, which desktop_click presses in the background. desktop_dialog with window \(parent)'s target reads it, and desktop_dialog_answer answers it."))]) + } if window["is_minimized"]?.bool == true { return .object([("state", "unavailable"), ("reason", "minimized"), ("hint", JSON("The window is minimized. Restore it with desktop_restore, using its target from desktop_windows, \(reinspect)"))]) diff --git a/native2/Sources/DesktopHost/Dialogs.swift b/native2/Sources/DesktopHost/Dialogs.swift index 6b960788..5fd1d544 100644 --- a/native2/Sources/DesktopHost/Dialogs.swift +++ b/native2/Sources/DesktopHost/Dialogs.swift @@ -55,6 +55,12 @@ private func dialogWindows(_ pid: pid_t) async -> [DialogWindow] { /// Reads the target's dialog; a native refusal reads as the bridge reported it, an unmatched dialog as the client did. private func readDialog(_ target: DialogTarget) async throws -> DialogRead { + // A sheet's own window holds no Accessibility tree; the sheet is read through the window it's attached to (#245). + if let id = target.window?.id, let listing = try? await Mac.windows(pid: target.pid), + let parent = listing["windows"]?.array?.first(where: { $0["window_id"]?.int == id })?["sheet_of_window_id"]?.int + { + throw BridgeError(message: "Window \(id) is a sheet attached to window \(parent), and Accessibility reaches the sheet only through window \(parent). Pass window \(parent)'s target from desktop_windows, or the app's target from desktop_apps") + } let windows = await dialogWindows(target.pid) let read: DialogRead do { diff --git a/native2/Sources/DesktopHost/Inspect.swift b/native2/Sources/DesktopHost/Inspect.swift index f19ddf5e..ed5319c1 100644 --- a/native2/Sources/DesktopHost/Inspect.swift +++ b/native2/Sources/DesktopHost/Inspect.swift @@ -59,6 +59,17 @@ extension Tools { } let save = try saveRequest(arguments) + // A sheet's own window has no Accessibility tree and takes no input; its parent's snapshot holds both (#245). + if request.scope == nil, let parent = try await run({ Mac.sheetParent(pid: pid, windowID: windowID) }) { + return ToolResult(text: JSON.object([ + "inspection_error": .object([("code", "ATTACHED_SHEET"), ("message", JSON( + "Window \(windowID) is a sheet attached to window \(parent). Accessibility reaches the sheet only through window \(parent), " + + "and input reaches it only through a snapshot of window \(parent). Inspect window \(parent) instead: its snapshot " + + "shows the sheet and lists it as an AXSheet with its text and buttons."))]), + "requested_target": .object([("pid", JSON(pid)), ("window_id", JSON(windowID)), ("mode", JSON(mode))]), + "parent_window_id": JSON(parent), + ]).text, isError: true) + } var retried = false let observed: Observation do { @@ -116,7 +127,14 @@ extension Tools { } static func observation(pid: pid_t, windowID: Int) async throws -> (JSON, (data: String, mimeType: String)) { - let observed = try await observation(pid: pid, windowID: windowID, InspectRequest(), started: .now) + let observed: Observation + do { + observed = try await observation(pid: pid, windowID: windowID, InspectRequest(), started: .now) + } catch let error as ToolError where error.message.range(of: "incomplete|sparse", options: [.regularExpression, .caseInsensitive]) != nil { + // Input that opens a sheet or alert often leaves the tree mid-change for a moment (#244), as inspect retries. + try await Task.sleep(for: .milliseconds(500)) + observed = try await observation(pid: pid, windowID: windowID, InspectRequest(), started: .now) + } return (observed.data, observed.image) } @@ -235,7 +253,7 @@ extension Tools { // loads rows as they're read grows and moves what's below, so a screenshot taken during the walk can show // content where it no longer is, and a point read off it misses (#159). let elements = try await run { try budget.map { try Mac.elements(pid: pid, windowID: windowID, budget: $0) } ?? ElementTree() } - let image = try await Mac.captureWindow(windowID, bounds: window.bounds) + let image = try await Mac.captureWindow(windowID, bounds: window.bounds, pid: pid) let captureDone = ContinuousClock.now guard Mac.generation(pid) == generation, let after = Mac.window(windowID), after.pid == pid, after.bounds == window.bounds else { throw ToolError("invalidRequest: Snapshot is stale: Exact observation window changed during the observation. Inspect it again.") diff --git a/native2/Sources/DesktopMac/AX.swift b/native2/Sources/DesktopMac/AX.swift index 50401869..750e758c 100644 --- a/native2/Sources/DesktopMac/AX.swift +++ b/native2/Sources/DesktopMac/AX.swift @@ -83,6 +83,41 @@ enum AX { let bounds: CGRect? let isMinimized: Bool? let isMain: Bool? + /// The sheets attached to the window, which the window server lists as windows of their own (#245). + var sheets: [Sheet] = [] + } + + /// An attached sheet: the window number Accessibility reports for it, which can be its parent's, and its bounds. + struct Sheet { + let id: Int? + let bounds: CGRect? + + /// Whether a window server row is this sheet's window. + func matches(id other: Int, bounds frame: CGRect) -> Bool { + if id == other { return true } + guard let bounds else { return false } + // AppKit's sheet window has the sheet's bounds; a SwiftUI alert's window adds a transparent margin around it. + return frame.insetBy(dx: -1, dy: -1).contains(bounds) && abs(bounds.midX - frame.midX) <= 2 && abs(bounds.midY - frame.midY) <= 2 + && frame.width - bounds.width <= 200 && frame.height - bounds.height <= 200 + } + } + + /// A window's attached sheets. A sheet's own window number is often unreadable, so its bounds identify it too. + static func sheets(of window: AXUIElement) -> [Sheet] { + sheetElements(of: window).map { sheet in + AXUIElementSetMessagingTimeout(sheet, 0.2) + return Sheet(id: windowID(sheet), bounds: frame(sheet)) + } + } + + /// A window's AXSheet elements: AppKit lists them among the window's children, and few apps support AXSheets. + static func sheetElements(of window: AXUIElement) -> [AXUIElement] { + var seen: [AXUIElement] = [] + for element in (value(window, "AXSheets") as? [AXUIElement] ?? []) + (value(window, kAXChildrenAttribute) as? [AXUIElement] ?? []) + where string(element, kAXRoleAttribute) == "AXSheet" && !seen.contains(where: { CFEqual($0, element) }) { + seen.append(element) + } + return seen } struct Windows { @@ -170,7 +205,8 @@ enum AX { title: fields[0] as? String ?? "", bounds: origin.flatMap { origin in size.map { CGRect(origin: origin, size: $0) } }, isMinimized: fields[3] as? Bool, - isMain: fields[4] as? Bool + isMain: fields[4] as? Bool, + sheets: remaining() > 0 ? sheets(of: window) : [] )) } return result diff --git a/native2/Sources/DesktopMac/Capture.swift b/native2/Sources/DesktopMac/Capture.swift index 68ac7aca..63f8658b 100644 --- a/native2/Sources/DesktopMac/Capture.swift +++ b/native2/Sources/DesktopMac/Capture.swift @@ -10,7 +10,7 @@ public let maxImage = 900_000 extension Mac { /// One window's pixels at one pixel per point, without its shadow, as Peekaboo captures them. - public static func captureWindow(_ windowID: Int, bounds: CGRect) async throws -> CGImage { + public static func captureWindow(_ windowID: Int, bounds: CGRect, opaque: Bool = true) async throws -> CGImage { // Peekaboo's capture gate checks the lock first; Node adds the final period after Peekaboo's own. if (CGSessionCopyCurrentDictionary() as? [String: Any])?["CGSSessionScreenIsLocked"] as? Bool == true { throw ToolError("DESKTOP_ERROR: Capture failed: Screen capture is unavailable while the macOS GUI session is locked. `screen list` can still report connected displays; unlock the active user session and retry..") @@ -29,7 +29,7 @@ extension Mac { config.scalesToFit = true config.includeChildWindows = false config.showsCursor = false - config.shouldBeOpaque = true + config.shouldBeOpaque = opaque config.backgroundColor = .clear let image: CGImage = try await SCScreenshotManager.captureImage( contentFilter: SCContentFilter(desktopIndependentWindow: window), configuration: config @@ -40,6 +40,29 @@ extension Mac { return image } + /// A window's pixels with the sheets attached to it drawn where they are, as the screen shows them. A sheet is a + /// window of its own, which a capture of its parent leaves out, though the parent's Accessibility tree holds its + /// elements (#245). A sheet that can't be captured leaves the parent's pixels as they are. + public static func captureWindow(_ windowID: Int, bounds: CGRect, pid: pid_t) async throws -> CGImage { + let image = try await captureWindow(windowID, bounds: bounds) + let sheets = attachedSheets(pid: pid, windowID: windowID).filter { bounds.intersects($0.bounds) } + guard !sheets.isEmpty else { return image } + var drawn: [(CGImage, CGRect)] = [] + for sheet in sheets { + if let pixels = try? await captureWindow(sheet.id, bounds: sheet.bounds, opaque: false) { drawn.append((pixels, sheet.bounds)) } + } + guard !drawn.isEmpty, let context = CGContext( + data: nil, width: image.width, height: image.height, bitsPerComponent: 8, bytesPerRow: 0, + space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { return image } + context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height)) + // The capture is one pixel per point, and a context's origin is its bottom left. + for (pixels, frame) in drawn { + context.draw(pixels, in: CGRect(x: frame.minX - bounds.minX, y: bounds.maxY - frame.maxY, width: frame.width, height: frame.height)) + } + return context.makeImage() ?? image + } + /// ScreenCaptureKit's window by ID. Its private per-window lookup takes about half as long as listing /// all shareable content, so try it first. static func scWindow(_ id: CGWindowID) async throws -> SCWindow? { diff --git a/native2/Sources/DesktopMac/Dialogs.swift b/native2/Sources/DesktopMac/Dialogs.swift index 560d3bae..f1ab1d15 100644 --- a/native2/Sources/DesktopMac/Dialogs.swift +++ b/native2/Sources/DesktopMac/Dialogs.swift @@ -289,6 +289,12 @@ private func onlyCandidate(pid: pid_t, windowID: Int?, windows: [DialogWindow], return (found[0], generation) } +/// A button's title, or its description where SwiftUI puts an alert button's title (#245). +private func buttonTitle(_ button: AXUIElement) -> String? { + [kAXTitleAttribute, kAXDescriptionAttribute].lazy.compactMap { text(button, $0) } + .first { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } +} + private func buttons(_ dialog: AXUIElement, budget: Budget) throws -> [AXUIElement] { try collect(dialog, roles: ["AXButton"], budget: budget) } @@ -414,7 +420,7 @@ extension Mac { "bounds": Mac.boundsJSON(AX.frame(dialog) ?? .zero), ]) let buttonRows = try buttons(dialog, budget: budget).compactMap { button -> (String, Bool, Bool)? in - guard let title = text(button, kAXTitleAttribute) else { return nil } + guard let title = buttonTitle(button) else { return nil } return (title, flag(button, kAXEnabledAttribute) ?? true, flag(button, "AXDefault") ?? false) } let fieldRows = try textFields(dialog, budget: budget).enumerated().map { index, field in @@ -445,16 +451,16 @@ extension Mac { let matches = all.filter { button in guard flag(button, kAXEnabledAttribute) == true, Mac.actions(button).contains(kAXPressAction) else { return false } if wanted == "default" { return text(button, kAXIdentifierAttribute) == "OKButton" } - return text(button, kAXTitleAttribute).map(normalized) == wanted + return buttonTitle(button).map(normalized) == wanted } guard matches.count == 1 else { - let available = all.enumerated().map { "\($0):\(text($1, kAXTitleAttribute) ?? "untitled")" }.joined(separator: ", ") + let available = all.enumerated().map { "\($0):\(buttonTitle($1) ?? "untitled")" }.joined(separator: ", ") throw refusal(matches.isEmpty ? "No unique enabled AXPress dialog button matched the request." : "Dialog button selection is ambiguous across \(matches.count) exact matches.", hint: "Candidate window ID: \(candidate.window.id). Available buttons: \(available.isEmpty ? "none" : available). Run dialog list and provide one unique exact button name.") } let button = matches[0] var details = targetDetails(pid, generation, candidate.window.id) - details += [("button", text(button, kAXTitleAttribute) ?? title), ("method", "button")] + details += [("button", buttonTitle(button) ?? title), ("method", "button")] if let identifier = text(button, kAXIdentifierAttribute), !identifier.isEmpty { details.append(("button_identifier", identifier)) } AXUIElementSetMessagingTimeout(button, 0) let leaf: Delivery diff --git a/native2/Sources/DesktopMac/Pointer.swift b/native2/Sources/DesktopMac/Pointer.swift index a3e460cc..4a2f88f8 100644 --- a/native2/Sources/DesktopMac/Pointer.swift +++ b/native2/Sources/DesktopMac/Pointer.swift @@ -396,7 +396,35 @@ extension Mac { private static func press(_ element: AXUIElement, window: ClickWindow) throws -> Delivery { try window.check() - return try finished(AXUIElementPerformAction(element, kAXPressAction as CFString), mechanism: "accessibility_action") + let sheets = sheetCount(window) + let delivery = try finished(AXUIElementPerformAction(element, kAXPressAction as CFString), mechanism: "accessibility_action") + guard delivery.state == .indeterminate, let sheets else { return delivery } + return sheetReadback(delivery, before: sheets, window: window) + } + + /// The number of sheets attached to the exact window, or nil when it can't be read. + static func sheetCount(_ window: ClickWindow) -> Int? { + let app = AXUIElementCreateApplication(window.pid) + AXUIElementSetMessagingTimeout(app, 0.5) + guard let ax = (AX.value(app, kAXWindowsAttribute) as? [AXUIElement])?.first(where: { AX.windowID($0) == window.windowID }) else { return nil } + AXUIElementSetMessagingTimeout(ax, 0.5) + return AX.sheetElements(of: ax).count + } + + /// An app that opens or closes a sheet in answer to a press often fails to reply to it (cannotComplete), though the + /// press took effect (#243). A sheet that appeared or went away within a second confirms the press. + static func sheetReadback(_ delivery: Delivery, before: Int, window: ClickWindow) -> Delivery { + let deadline = ContinuousClock.now + .seconds(1) + while ContinuousClock.now < deadline { + if let now = sheetCount(window), now != before { + var confirmed = Delivery(state: .confirmedChange, mechanism: delivery.mechanism, evidence: "verified_change", units: delivery.units) + confirmed.fields = delivery.fields.filter { $0.0 != "ax_error" } + confirmed.fields.append(("sheet", JSON(now > before ? "opened" : "closed"))) + return confirmed + } + Thread.sleep(forTimeInterval: 0.05) + } + return delivery } private static func finished(_ error: AXError, mechanism: String) throws -> Delivery { @@ -407,7 +435,9 @@ extension Mac { throw Refusal("ELEMENT_NOT_FOUND", "The element is gone from the window. Inspect it again.") default: // The app may have acted and then failed to reply in time. - return Delivery(state: .indeterminate, mechanism: mechanism, evidence: "completion_unknown", units: 1) + var delivery = Delivery(state: .indeterminate, mechanism: mechanism, evidence: "completion_unknown", units: 1) + delivery.fields.append(("ax_error", JSON(Int(error.rawValue)))) + return delivery } } diff --git a/native2/Sources/DesktopMac/Windows.swift b/native2/Sources/DesktopMac/Windows.swift index 4862562a..c209af51 100644 --- a/native2/Sources/DesktopMac/Windows.swift +++ b/native2/Sources/DesktopMac/Windows.swift @@ -53,6 +53,8 @@ private struct Row { var isKey: Bool? var capability: String var reason: String? + /// The window this one is attached to as a sheet. + var sheetOf: Int? } private func plural(_ n: Int, _ noun: String) -> String { "\(n) \(noun)\(n == 1 ? "" : "s")" } @@ -110,6 +112,16 @@ extension Mac { } var row = Row(id: cg.id, title: cg.title, bounds: cg.bounds, isOnScreen: cg.isOnScreen, isMinimized: cg.parked, isKey: nil, capability: unmatched.0, reason: unmatched.1) + // A sheet is its own window to the window server, but Accessibility reaches it only as its parent's + // AXSheet, and input reaches it only through a snapshot of the parent (#245). + if !ax.descriptors.contains(where: { $0.id == cg.id }), + let parent = ax.descriptors.first(where: { parent in parent.id != cg.id && parent.sheets.contains { $0.matches(id: cg.id, bounds: cg.bounds) } }), + let parentID = parent.id + { + row.capability = "pixels_only" + row.reason = "attached_sheet" + row.sheetOf = parentID + } if let ax = ax.descriptors.first(where: { $0.id == cg.id }) { row.isMinimized = ax.isMinimized ?? cg.parked row.isKey = key(cg.id) @@ -188,6 +200,7 @@ extension Mac { "display_asleep": place.asleep.map(JSON.bool), "observation_capability": JSON(row.capability), "observation_reason": row.reason.map(JSON.string), + "sheet_of_window_id": row.sheetOf.map { JSON($0) }, "process_start_identity_decimal": identity, "target": .object([ ("pid", JSON(pid)), ("process_start_identity_decimal", identity), ("window_id", JSON(row.id)), @@ -201,6 +214,41 @@ extension Mac { } } +extension Mac { + /// The on-screen windows of the sheets attached to a window, frontmost last, which a capture of the window alone + /// leaves out (#245). + /// The window an untitled window is attached to as a sheet, if it is one (#245). + public static func sheetParent(pid: pid_t, windowID: Int) -> Int? { + guard let window = window(windowID), window.pid == pid, window.title.isEmpty else { return nil } + let app = AXUIElementCreateApplication(pid) + AXUIElementSetMessagingTimeout(app, 0.5) + let windows = AX.value(app, kAXWindowsAttribute) as? [AXUIElement] ?? [] + var ids: [Int?] = [] + for candidate in windows { + AXUIElementSetMessagingTimeout(candidate, 0.5) + ids.append(AX.windowID(candidate)) + } + guard !ids.contains(windowID) else { return nil } + for (candidate, id) in zip(windows, ids) { + if let id, AX.sheets(of: candidate).contains(where: { $0.matches(id: windowID, bounds: window.bounds) }) { return id } + } + return nil + } + + public static func attachedSheets(pid: pid_t, windowID: Int) -> [(id: Int, bounds: CGRect)] { + let app = AXUIElementCreateApplication(pid) + AXUIElementSetMessagingTimeout(app, 0.5) + guard let window = (AX.value(app, kAXWindowsAttribute) as? [AXUIElement] ?? []).first(where: { AX.windowID($0) == windowID }) else { return [] } + AXUIElementSetMessagingTimeout(window, 0.5) + let sheets = AX.sheets(of: window) + guard !sheets.isEmpty else { return [] } + // The window list runs front to back. + return cgRows().reversed().filter { row in + row.pid == pid && row.id != windowID && row.layer == 0 && row.isOnScreen && sheets.contains { $0.matches(id: row.id, bounds: row.bounds) } + }.map { ($0.id, $0.bounds) } + } +} + /// The window that pointer events at a point would reach, as a refusal or an unverified hover names it. public struct WindowAtPoint: Sendable, Equatable { public let id: Int diff --git a/native2/Sources/DesktopProtocol/ContractText.swift b/native2/Sources/DesktopProtocol/ContractText.swift index e340f414..b91a0157 100644 --- a/native2/Sources/DesktopProtocol/ContractText.swift +++ b/native2/Sources/DesktopProtocol/ContractText.swift @@ -118,7 +118,7 @@ Recipes for multi-step tasks, such as printing to PDF, installing from a DMG or }, { "name": "desktop_windows", - "description": "List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable…) and a hint naming the tool that recovers it; check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.", + "description": "List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable, attached_sheet…) and a hint naming the tool that recovers it; a sheet or alert is listed as a window of its own but is inspected, clicked and answered through the window it is attached to (sheet_of_window_id); check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.", "inputSchema": { "type": "object", "required": [ diff --git a/native2/Tests/CoreTests/CoreTests.swift b/native2/Tests/CoreTests/CoreTests.swift index cf96e2ec..47375b8d 100644 --- a/native2/Tests/CoreTests/CoreTests.swift +++ b/native2/Tests/CoreTests/CoreTests.swift @@ -61,6 +61,15 @@ import Testing #expect(result["state"] == "unavailable") } + @Test func attachedSheetPointsToItsParent() { + // Sheets are often listed off screen while plainly showing; the parent is the route either way (#245). + let row: JSON = .object([("is_on_screen", false), ("observation_capability", "pixels_only"), ("observation_reason", "attached_sheet"), ("sheet_of_window_id", 41)]) + let result = windowAvailability(row, sessionLocked: false, appHidden: false) + #expect(result["reason"] == "attached_sheet") + #expect(result["parent_window_id"] == 41) + #expect(result["hint"]?.string?.contains("Inspect window 41") == true) + } + @Test func lockedComesFirst() { let row: JSON = .object([("is_minimized", true)]) #expect(windowAvailability(row, sessionLocked: true, appHidden: true)["reason"] == "session_locked") diff --git a/native2/fixture/sheetfix.swift b/native2/fixture/sheetfix.swift new file mode 100644 index 00000000..e8a630a5 --- /dev/null +++ b/native2/fixture/sheetfix.swift @@ -0,0 +1,96 @@ +import AppKit +import SwiftUI +// The sheet fixture (#243, #245): an AppKit window whose buttons open a custom sheet with beginSheet and an NSAlert +// sheet, and a second window hosting a SwiftUI view with .alert(isPresented:). Every sheet's buttons close it, and +// the fixture logs each open, close and button to stdout, so a test can tell a click that worked from one that didn't. +// Build it with `swiftc -O native2/fixture/sheetfix.swift -o native2/.build/sheetfix`; the process is "sheetfix". +func log(_ s: String) { print(s); fflush(stdout) } + +final class Controller: NSObject { + let window: NSWindow + var sheet: NSWindow? + + init(window: NSWindow) { self.window = window } + + @objc func openSheet(_ sender: Any?) { + let sheet = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 320, height: 140), styleMask: [.titled], backing: .buffered, defer: false) + let label = NSTextField(labelWithString: "Delete the selected item?") + label.frame = NSRect(x: 20, y: 90, width: 280, height: 22) + let cancel = NSButton(title: "Cancel", target: self, action: #selector(closeSheet(_:))) + cancel.frame = NSRect(x: 120, y: 20, width: 90, height: 32) + cancel.keyEquivalent = "\u{1b}" + let delete = NSButton(title: "Delete", target: self, action: #selector(closeSheet(_:))) + delete.frame = NSRect(x: 215, y: 20, width: 90, height: 32) + delete.keyEquivalent = "\r" + for view in [label, cancel, delete] { sheet.contentView!.addSubview(view) } + self.sheet = sheet + log("sheet open") + window.beginSheet(sheet) { response in log("sheet closed \(response.rawValue)") } + } + + @objc func closeSheet(_ sender: NSButton) { + log("sheet button \(sender.title)") + guard let sheet else { return } + window.endSheet(sheet, returnCode: sender.title == "Delete" ? .OK : .cancel) + self.sheet = nil + } + + @objc func openAlert(_ sender: Any?) { + let alert = NSAlert() + alert.messageText = "Remove the file?" + alert.informativeText = "This can't be undone." + alert.addButton(withTitle: "Remove") + alert.addButton(withTitle: "Cancel") + log("alert open") + alert.beginSheetModal(for: window) { response in log("alert closed \(response.rawValue)") } + } +} + +struct AlertView: View { + @State private var showing = false + @State private var status = "No answer yet" + + var body: some View { + VStack(spacing: 16) { + Text(status) + Button("Show Alert") { log("swiftui alert open"); showing = true } + } + .frame(width: 360, height: 220) + .alert("Delete project?", isPresented: $showing) { + Button("Delete", role: .destructive) { log("swiftui alert Delete"); status = "Deleted" } + Button("Cancel", role: .cancel) { log("swiftui alert Cancel"); status = "Cancelled" } + } message: { + Text("The project and its files will be removed.") + } + } +} + +let app = NSApplication.shared +app.setActivationPolicy(.regular) +let menu = NSMenu() +let appItem = NSMenuItem() +appItem.submenu = NSMenu(title: "sheetfix") +appItem.submenu!.addItem(withTitle: "Quit sheetfix", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") +menu.addItem(appItem) +app.mainMenu = menu + +let window = NSWindow(contentRect: NSRect(x: 200, y: 300, width: 420, height: 260), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false) +window.title = "Sheet Fixture" +window.isReleasedWhenClosed = false +let controller = Controller(window: window) +let openSheet = NSButton(title: "Open Sheet", target: controller, action: #selector(Controller.openSheet(_:))) +openSheet.frame = NSRect(x: 40, y: 160, width: 140, height: 32) +let openAlert = NSButton(title: "Open Alert", target: controller, action: #selector(Controller.openAlert(_:))) +openAlert.frame = NSRect(x: 220, y: 160, width: 140, height: 32) +window.contentView!.addSubview(openSheet) +window.contentView!.addSubview(openAlert) +window.makeKeyAndOrderFront(nil) + +let swiftUIWindow = NSWindow(contentRect: NSRect(x: 680, y: 300, width: 360, height: 220), styleMask: [.titled, .closable], backing: .buffered, defer: false) +swiftUIWindow.title = "SwiftUI Alert" +swiftUIWindow.isReleasedWhenClosed = false +swiftUIWindow.contentView = NSHostingView(rootView: AlertView()) +swiftUIWindow.orderFront(nil) + +log("ready") +app.run() diff --git a/src/tools.ts b/src/tools.ts index e346b5e9..571384a8 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -254,7 +254,7 @@ export function desktopTools({ place, app }: { place: string; app: string; wire? name: "desktop_windows", op: "windows", description: - `List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable…) and a hint naming the tool that recovers it; check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.`, + `List one app's windows by pid, with the target that window tools take. Read-only. Each row's availability gives state (available, limited or unavailable), a reason (minimized, app_hidden, other_space, off_screen, session_locked, display_asleep, accessibility_unavailable, attached_sheet…) and a hint naming the tool that recovers it; a sheet or alert is listed as a window of its own but is inspected, clicked and answered through the window it is attached to (sheet_of_window_id); check it before acting, since nothing recovers automatically. Rows also carry space_ids, and display_index off the main display. Untitled windows off screen and on no Space, such as menu bar strips, are only counted in hidden_windows; all true lists them.`, parameters: { type: "object", required: ["pid"], From 9d79269fd3cfad3ef7fd7d0d25316ec367f01e69 Mon Sep 17 00:00:00 2001 From: iamnbutler Date: Thu, 12 Nov 2026 14:44:06 -0500 Subject: [PATCH 2/2] lane: confirm notes whose files changed Co-Authored-By: Claude Opus 5.5 --- ...M5BQ1V3CPQDWYQTHDMSTVKER-node-reports-a-bridge-listin.md | 6 +++--- ...M6EGHDB0SCEQPQ1YZM1GXT3P-ui-elements-are-ordered-by-p.md | 5 +++-- ...M5CN92TYYG84ZVVQ0GX9BZFQ-chrome-builds-its-web-tree-2.md | 5 +++-- ...M6ERA1J7SSQA8K5R8XAZ89KG-the-idle-restore-waits-as-lo.md | 5 +++-- ...M5BRY353MWEM4A1CC6NR283Q-node-s-table-rows-omit-eleme.md | 6 +++--- ...M5BN9KJQFE53RTHPE4HXGWVA-node-checks-expect-before-an.md | 6 +++--- ...M5BM7D1DAR3BRHR4X4S1XW0Z-app-conditions-read-the-whol.md | 6 +++--- ...M5BT5RNAFB1QEGEX11BG8SCG-dialog-waits-by-pid-alone-re.md | 6 +++--- ...M5BVDDZT4TGXBJ9W62EWMKE9-peekaboo-s-element-read-find.md | 6 +++--- ...M5CPSX02HCTBWDMCP17Q329B-textedit-and-finder-drop-a-b.md | 5 +++-- ...M6FFPDVQC6AA25Y4VYQ9JS2Z-two-title-bar-preparation-cl.md | 5 +++-- ...M5CPSX084MHH9FZMPG40KXMQ-a-sheet-and-go-to-folder-ins.md | 5 +++-- ...1M6ERA1HWXX312TKFNDSR4N14-visibleonly-is-for-unscoped.md | 5 +++-- ...1M5BMJP957GVWF5WSKEGK2ZAZ-appkit-ignores-a-background.md | 6 +++--- ...M5BMJP9FAVZ10AN2ZR29QMN1-key-codes-for-characters-com.md | 6 +++--- ...M5BM221JZ273DQQTVKJVZBQW-routed-events-name-the-windo.md | 6 +++--- ...M5BVVPB930XQY34PWD5NYMMY-the-point-click-descendant-w.md | 6 +++--- ...M6FH0PW0B2MSDCKFQ89Z7W8A-a-routed-left-click-into-a-c.md | 6 +++--- ...M6FH414M25XSGQ0GX40CHS7N-chromium-exposes-axpress-onl.md | 6 +++--- ...M6ERA1JHEXYBSGS6QS9J3JHZ-only-launches-this-host-turn.md | 5 +++-- ...M5D5AB2Y625F1Q2BN8VF36VS-safari-on-macos-27-dropped-a.md | 6 +++--- ...M5D67S6R6K06W6RAC521M5X6-safari-focuses-its-address-f.md | 5 +++-- ...M5BVVPB06PKGFH4H2NMDXAYN-the-window-server-moves-a-wi.md | 5 +++-- ...M6EGHDC3CRDZA07DMY7W1EHM-files-ships-native2-licenses.md | 5 +++-- ...M6EGHDDEY51YSVQ7T6TS53SX-this-text-is-what-native2-se.md | 6 +++--- 25 files changed, 75 insertions(+), 64 deletions(-) diff --git a/.lane/memory/native2/Sources/DesktopHost/Dialogs.swift/01M5BQ1V3CPQDWYQTHDMSTVKER-node-reports-a-bridge-listin.md b/.lane/memory/native2/Sources/DesktopHost/Dialogs.swift/01M5BQ1V3CPQDWYQTHDMSTVKER-node-reports-a-bridge-listin.md index 35a930b2..b312a654 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Dialogs.swift/01M5BQ1V3CPQDWYQTHDMSTVKER-node-reports-a-bridge-listin.md +++ b/.lane/memory/native2/Sources/DesktopHost/Dialogs.swift/01M5BQ1V3CPQDWYQTHDMSTVKER-node-reports-a-bridge-listin.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:53:00Z norm: '1' sig: 9cb6c3e49e9e89a4 -body_hash: 3841b3ec6afb2924 -raw_hash: df11d5a8cf5c0636 -vouched: 2026-10-20T13:08:22Z +body_hash: 3341f836eb7b413b +raw_hash: c1f9af46cfeac4e0 +vouched: 2026-11-12T19:43:59Z lines: 1-286 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Inspect.swift/01M6EGHDB0SCEQPQ1YZM1GXT3P-ui-elements-are-ordered-by-p.md b/.lane/memory/native2/Sources/DesktopHost/Inspect.swift/01M6EGHDB0SCEQPQ1YZM1GXT3P-ui-elements-are-ordered-by-p.md index 838e30ad..0fb2eb15 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Inspect.swift/01M6EGHDB0SCEQPQ1YZM1GXT3P-ui-elements-are-ordered-by-p.md +++ b/.lane/memory/native2/Sources/DesktopHost/Inspect.swift/01M6EGHDB0SCEQPQ1YZM1GXT3P-ui-elements-are-ordered-by-p.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-02T16:12:36Z norm: '1' sig: 9cb6c3e49e9e89a4 -body_hash: 9d9ffaccaeee4733 -raw_hash: e23bc0e0a622ec5d +body_hash: d24fe84faca71ef1 +raw_hash: 285317310b611c97 +vouched: 2026-11-12T19:43:59Z lines: 1-308 supersedes: 01M5BKJWVVGTA2MXXFH8WD7ADT --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M5CN92TYYG84ZVVQ0GX9BZFQ-chrome-builds-its-web-tree-2.md b/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M5CN92TYYG84ZVVQ0GX9BZFQ-chrome-builds-its-web-tree-2.md index aafed135..51ad7899 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M5CN92TYYG84ZVVQ0GX9BZFQ-chrome-builds-its-web-tree-2.md +++ b/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M5CN92TYYG84ZVVQ0GX9BZFQ-chrome-builds-its-web-tree-2.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-20T12:41:15Z norm: '1' sig: 9cb6c3e49e9e89a4 -body_hash: 8a9ea0714973a82a -raw_hash: 74a0e29a25be2c3a +body_hash: 834a3cc77b499908 +raw_hash: 0a8adc11be29a95e +vouched: 2026-11-12T19:43:59Z lines: 1-133 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M6ERA1J7SSQA8K5R8XAZ89KG-the-idle-restore-waits-as-lo.md b/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M6ERA1J7SSQA8K5R8XAZ89KG-the-idle-restore-waits-as-lo.md index 751cae58..ce06375f 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M6ERA1J7SSQA8K5R8XAZ89KG-the-idle-restore-waits-as-lo.md +++ b/.lane/memory/native2/Sources/DesktopHost/Scope.swift/01M6ERA1J7SSQA8K5R8XAZ89KG-the-idle-restore-waits-as-lo.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-02T18:28:23Z norm: '1' sig: 9cb6c3e49e9e89a4 -body_hash: 1a1907423feee250 -raw_hash: 9b656a63ea16121d +body_hash: 834a3cc77b499908 +raw_hash: 0a8adc11be29a95e +vouched: 2026-11-12T19:43:59Z lines: 1-169 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/TextRead.swift/01M5BRY353MWEM4A1CC6NR283Q-node-s-table-rows-omit-eleme.md b/.lane/memory/native2/Sources/DesktopHost/TextRead.swift/01M5BRY353MWEM4A1CC6NR283Q-node-s-table-rows-omit-eleme.md index 7a34f5d3..2341120e 100644 --- a/.lane/memory/native2/Sources/DesktopHost/TextRead.swift/01M5BRY353MWEM4A1CC6NR283Q-node-s-table-rows-omit-eleme.md +++ b/.lane/memory/native2/Sources/DesktopHost/TextRead.swift/01M5BRY353MWEM4A1CC6NR283Q-node-s-table-rows-omit-eleme.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T04:25:55Z norm: '1' sig: 9cb6c3e49e9e89a4 -body_hash: d7ab3a923729569f -raw_hash: 490623e9c71688bc -vouched: 2026-10-24T23:36:33Z +body_hash: d98f3ea8385575ae +raw_hash: 0204b97245343640 +vouched: 2026-11-12T19:43:59Z lines: 1-831 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Tools.swift/01M5BN9KJQFE53RTHPE4HXGWVA-node-checks-expect-before-an.md b/.lane/memory/native2/Sources/DesktopHost/Tools.swift/01M5BN9KJQFE53RTHPE4HXGWVA-node-checks-expect-before-an.md index 83be1367..0b547579 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Tools.swift/01M5BN9KJQFE53RTHPE4HXGWVA-node-checks-expect-before-an.md +++ b/.lane/memory/native2/Sources/DesktopHost/Tools.swift/01M5BN9KJQFE53RTHPE4HXGWVA-node-checks-expect-before-an.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:22:18Z norm: '1' sig: 0fd09e8458d728cc -body_hash: 3b0abb299ffcfe0b -raw_hash: 62455994b90c7cfa -vouched: 2026-10-20T12:05:54Z +body_hash: 78bc32d6c8786781 +raw_hash: b9160a43193908ae +vouched: 2026-11-12T19:43:59Z lines: 1-131 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BM7D1DAR3BRHR4X4S1XW0Z-app-conditions-read-the-whol.md b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BM7D1DAR3BRHR4X4S1XW0Z-app-conditions-read-the-whol.md index c13a3978..0a44cb8f 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BM7D1DAR3BRHR4X4S1XW0Z-app-conditions-read-the-whol.md +++ b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BM7D1DAR3BRHR4X4S1XW0Z-app-conditions-read-the-whol.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:03:37Z norm: '1' sig: 0fd09e8458d728cc -body_hash: 18d014746c1f0441 -raw_hash: d33974560c2d7845 -vouched: 2026-10-20T11:31:44Z +body_hash: 76d7ebe225d14687 +raw_hash: 271356e3bc645c7b +vouched: 2026-11-12T19:43:59Z lines: 1-646 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BT5RNAFB1QEGEX11BG8SCG-dialog-waits-by-pid-alone-re.md b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BT5RNAFB1QEGEX11BG8SCG-dialog-waits-by-pid-alone-re.md index c50ba878..9a7c9f5a 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BT5RNAFB1QEGEX11BG8SCG-dialog-waits-by-pid-alone-re.md +++ b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BT5RNAFB1QEGEX11BG8SCG-dialog-waits-by-pid-alone-re.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T04:47:35Z norm: '1' sig: 0fd09e8458d728cc -body_hash: 18d014746c1f0441 -raw_hash: d33974560c2d7845 -vouched: 2026-10-20T11:31:44Z +body_hash: 76d7ebe225d14687 +raw_hash: 271356e3bc645c7b +vouched: 2026-11-12T19:43:59Z lines: 1-646 --- diff --git a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BVDDZT4TGXBJ9W62EWMKE9-peekaboo-s-element-read-find.md b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BVDDZT4TGXBJ9W62EWMKE9-peekaboo-s-element-read-find.md index c9eb6e48..ad57aef1 100644 --- a/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BVDDZT4TGXBJ9W62EWMKE9-peekaboo-s-element-read-find.md +++ b/.lane/memory/native2/Sources/DesktopHost/Wait.swift/01M5BVDDZT4TGXBJ9W62EWMKE9-peekaboo-s-element-read-find.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T05:09:14Z norm: '1' sig: 0fd09e8458d728cc -body_hash: 18d014746c1f0441 -raw_hash: d33974560c2d7845 -vouched: 2026-10-20T11:31:44Z +body_hash: 76d7ebe225d14687 +raw_hash: 271356e3bc645c7b +vouched: 2026-11-12T19:43:59Z lines: 1-646 supersedes: 01M5BPTDK4VCACPY2GS3CRBSTF --- diff --git a/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M5CPSX02HCTBWDMCP17Q329B-textedit-and-finder-drop-a-b.md b/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M5CPSX02HCTBWDMCP17Q329B-textedit-and-finder-drop-a-b.md index 7b4711c4..9a9c5aca 100644 --- a/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M5CPSX02HCTBWDMCP17Q329B-textedit-and-finder-drop-a-b.md +++ b/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M5CPSX02HCTBWDMCP17Q329B-textedit-and-finder-drop-a-b.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-20T13:07:54Z norm: '1' sig: 27abcba5e975f16e -body_hash: 32e37c9908746f04 -raw_hash: a3a043dc83705494 +body_hash: 156e41d5cd6594ec +raw_hash: 76d0ecf4f3fe0413 +vouched: 2026-11-12T19:43:59Z lines: 1-164 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M6FFPDVQC6AA25Y4VYQ9JS2Z-two-title-bar-preparation-cl.md b/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M6FFPDVQC6AA25Y4VYQ9JS2Z-two-title-bar-preparation-cl.md index 52c1e2c4..f40feb7d 100644 --- a/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M6FFPDVQC6AA25Y4VYQ9JS2Z-two-title-bar-preparation-cl.md +++ b/.lane/memory/native2/Sources/DesktopMac/BackgroundPreparation.swift/01M6FFPDVQC6AA25Y4VYQ9JS2Z-two-title-bar-preparation-cl.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-03T01:17:06Z norm: '1' sig: 27abcba5e975f16e -body_hash: b6118ea4420e4b5c -raw_hash: 6a1f1910291f43cd +body_hash: 156e41d5cd6594ec +raw_hash: 76d0ecf4f3fe0413 +vouched: 2026-11-12T19:43:59Z lines: 1-171 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Dialogs.swift/01M5CPSX084MHH9FZMPG40KXMQ-a-sheet-and-go-to-folder-ins.md b/.lane/memory/native2/Sources/DesktopMac/Dialogs.swift/01M5CPSX084MHH9FZMPG40KXMQ-a-sheet-and-go-to-folder-ins.md index dcfe7e83..2f9f7c78 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Dialogs.swift/01M5CPSX084MHH9FZMPG40KXMQ-a-sheet-and-go-to-folder-ins.md +++ b/.lane/memory/native2/Sources/DesktopMac/Dialogs.swift/01M5CPSX084MHH9FZMPG40KXMQ-a-sheet-and-go-to-folder-ins.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-20T13:07:55Z norm: '1' sig: 27abcba5e975f16e -body_hash: e326dd2d74b29cdb -raw_hash: 912c5d321737d476 +body_hash: c618a4c2409e17b6 +raw_hash: f26d1ad337533364 +vouched: 2026-11-12T19:43:59Z lines: 1-846 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Elements.swift/01M6ERA1HWXX312TKFNDSR4N14-visibleonly-is-for-unscoped.md b/.lane/memory/native2/Sources/DesktopMac/Elements.swift/01M6ERA1HWXX312TKFNDSR4N14-visibleonly-is-for-unscoped.md index be4a0720..c44eebfa 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Elements.swift/01M6ERA1HWXX312TKFNDSR4N14-visibleonly-is-for-unscoped.md +++ b/.lane/memory/native2/Sources/DesktopMac/Elements.swift/01M6ERA1HWXX312TKFNDSR4N14-visibleonly-is-for-unscoped.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-02T18:28:23Z norm: '1' sig: 1574ff353918c008 -body_hash: 0eeeb80ab28ee018 -raw_hash: 311343ab0a3723b4 +body_hash: 937d6a95b016e8c7 +raw_hash: 5bbca8fb1b1434d4 +vouched: 2026-11-12T19:43:59Z lines: 1-526 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP957GVWF5WSKEGK2ZAZ-appkit-ignores-a-background.md b/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP957GVWF5WSKEGK2ZAZ-appkit-ignores-a-background.md index 7dd3969b..bce774e2 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP957GVWF5WSKEGK2ZAZ-appkit-ignores-a-background.md +++ b/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP957GVWF5WSKEGK2ZAZ-appkit-ignores-a-background.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:09:47Z norm: '1' sig: 27abcba5e975f16e -body_hash: 22b623691bf046c2 -raw_hash: c51598ff808c374f -vouched: 2026-11-02T16:12:35Z +body_hash: 9e26e542c95e910f +raw_hash: b496f2c41bd71769 +vouched: 2026-11-12T19:43:59Z lines: 1-373 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP9FAVZ10AN2ZR29QMN1-key-codes-for-characters-com.md b/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP9FAVZ10AN2ZR29QMN1-key-codes-for-characters-com.md index 6ba47d6f..8728094f 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP9FAVZ10AN2ZR29QMN1-key-codes-for-characters-com.md +++ b/.lane/memory/native2/Sources/DesktopMac/Keyboard.swift/01M5BMJP9FAVZ10AN2ZR29QMN1-key-codes-for-characters-com.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:09:47Z norm: '1' sig: 27abcba5e975f16e -body_hash: 22b623691bf046c2 -raw_hash: c51598ff808c374f -vouched: 2026-11-02T16:12:35Z +body_hash: 9e26e542c95e910f +raw_hash: b496f2c41bd71769 +vouched: 2026-11-12T19:43:59Z lines: 1-373 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BM221JZ273DQQTVKJVZBQW-routed-events-name-the-windo.md b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BM221JZ273DQQTVKJVZBQW-routed-events-name-the-windo.md index 2a6099da..81659bcd 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BM221JZ273DQQTVKJVZBQW-routed-events-name-the-windo.md +++ b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BM221JZ273DQQTVKJVZBQW-routed-events-name-the-windo.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T03:00:42Z norm: '1' sig: 27abcba5e975f16e -body_hash: 99f766697f5cf49a -raw_hash: 2e2cf668fd7553e6 -vouched: 2026-11-03T02:45:55Z +body_hash: 80bd99e2dbc88f58 +raw_hash: 3d0594b030addc3a +vouched: 2026-11-12T19:43:59Z lines: 1-360 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BVVPB930XQY34PWD5NYMMY-the-point-click-descendant-w.md b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BVVPB930XQY34PWD5NYMMY-the-point-click-descendant-w.md index 69560ba4..d4a73feb 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BVVPB930XQY34PWD5NYMMY-the-point-click-descendant-w.md +++ b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M5BVVPB930XQY34PWD5NYMMY-the-point-click-descendant-w.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T05:17:02Z norm: '1' sig: 27abcba5e975f16e -body_hash: 99f766697f5cf49a -raw_hash: 2e2cf668fd7553e6 -vouched: 2026-11-03T02:45:55Z +body_hash: 80bd99e2dbc88f58 +raw_hash: 3d0594b030addc3a +vouched: 2026-11-12T19:43:59Z lines: 1-360 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH0PW0B2MSDCKFQ89Z7W8A-a-routed-left-click-into-a-c.md b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH0PW0B2MSDCKFQ89Z7W8A-a-routed-left-click-into-a-c.md index 959d2b5e..b06a6ba5 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH0PW0B2MSDCKFQ89Z7W8A-a-routed-left-click-into-a-c.md +++ b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH0PW0B2MSDCKFQ89Z7W8A-a-routed-left-click-into-a-c.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-11-03T01:40:11Z norm: '1' sig: 27abcba5e975f16e -body_hash: 99f766697f5cf49a -raw_hash: 2e2cf668fd7553e6 -vouched: 2026-11-03T02:45:55Z +body_hash: 80bd99e2dbc88f58 +raw_hash: 3d0594b030addc3a +vouched: 2026-11-12T19:44:00Z lines: 1-560 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH414M25XSGQ0GX40CHS7N-chromium-exposes-axpress-onl.md b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH414M25XSGQ0GX40CHS7N-chromium-exposes-axpress-onl.md index 38583c07..621ff42f 100644 --- a/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH414M25XSGQ0GX40CHS7N-chromium-exposes-axpress-onl.md +++ b/.lane/memory/native2/Sources/DesktopMac/Pointer.swift/01M6FH414M25XSGQ0GX40CHS7N-chromium-exposes-axpress-onl.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-11-03T01:42:00Z norm: '1' sig: 27abcba5e975f16e -body_hash: 99f766697f5cf49a -raw_hash: 2e2cf668fd7553e6 -vouched: 2026-11-03T02:45:55Z +body_hash: 80bd99e2dbc88f58 +raw_hash: 3d0594b030addc3a +vouched: 2026-11-12T19:44:00Z lines: 1-560 supersedes: 01M6ERHPXM4ZVJSQWDFV4GWZBF --- diff --git a/.lane/memory/native2/Sources/DesktopMac/WebAccessibility.swift/01M6ERA1JHEXYBSGS6QS9J3JHZ-only-launches-this-host-turn.md b/.lane/memory/native2/Sources/DesktopMac/WebAccessibility.swift/01M6ERA1JHEXYBSGS6QS9J3JHZ-only-launches-this-host-turn.md index 255e03fb..39e49635 100644 --- a/.lane/memory/native2/Sources/DesktopMac/WebAccessibility.swift/01M6ERA1JHEXYBSGS6QS9J3JHZ-only-launches-this-host-turn.md +++ b/.lane/memory/native2/Sources/DesktopMac/WebAccessibility.swift/01M6ERA1JHEXYBSGS6QS9J3JHZ-only-launches-this-host-turn.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-02T18:28:23Z norm: '1' sig: 27abcba5e975f16e -body_hash: 4a1a6138d04e81d2 -raw_hash: 9e7decda7e8dffa8 +body_hash: 8adb37cdd2e8385a +raw_hash: 03c72e9fbe8856b7 +vouched: 2026-11-12T19:44:00Z lines: 1-152 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D5AB2Y625F1Q2BN8VF36VS-safari-on-macos-27-dropped-a.md b/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D5AB2Y625F1Q2BN8VF36VS-safari-on-macos-27-dropped-a.md index 4fcb4b27..e23802f1 100644 --- a/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D5AB2Y625F1Q2BN8VF36VS-safari-on-macos-27-dropped-a.md +++ b/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D5AB2Y625F1Q2BN8VF36VS-safari-on-macos-27-dropped-a.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-10-20T17:21:33Z norm: '1' sig: 1574ff353918c008 -body_hash: 6243d6c3b242a4db -raw_hash: 5bd16c78dfed21ca -vouched: 2026-10-20T17:37:48Z +body_hash: e241e3e0cd80128c +raw_hash: b52824f29bc8ce91 +vouched: 2026-11-12T19:44:00Z lines: 1-182 --- diff --git a/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D67S6R6K06W6RAC521M5X6-safari-focuses-its-address-f.md b/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D67S6R6K06W6RAC521M5X6-safari-focuses-its-address-f.md index a16d620f..9e32d3c8 100644 --- a/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D67S6R6K06W6RAC521M5X6-safari-focuses-its-address-f.md +++ b/.lane/memory/native2/Sources/DesktopMac/WebText.swift/01M5D67S6R6K06W6RAC521M5X6-safari-focuses-its-address-f.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-20T17:37:38Z norm: '1' sig: 1574ff353918c008 -body_hash: 6243d6c3b242a4db -raw_hash: 5bd16c78dfed21ca +body_hash: e241e3e0cd80128c +raw_hash: b52824f29bc8ce91 +vouched: 2026-11-12T19:44:00Z lines: 1-191 --- diff --git a/.lane/memory/native2/Sources/DesktopTools/Window.swift/01M5BVVPB06PKGFH4H2NMDXAYN-the-window-server-moves-a-wi.md b/.lane/memory/native2/Sources/DesktopTools/Window.swift/01M5BVVPB06PKGFH4H2NMDXAYN-the-window-server-moves-a-wi.md index 42c2d37e..a3bc7d58 100644 --- a/.lane/memory/native2/Sources/DesktopTools/Window.swift/01M5BVVPB06PKGFH4H2NMDXAYN-the-window-server-moves-a-wi.md +++ b/.lane/memory/native2/Sources/DesktopTools/Window.swift/01M5BVVPB06PKGFH4H2NMDXAYN-the-window-server-moves-a-wi.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-10-20T05:17:02Z norm: '1' sig: b318dcdf7403555b -body_hash: 036cca83f9058e03 -raw_hash: 7e61420b94593d3e +body_hash: 04b32048b59b385f +raw_hash: e618ddbc1c9a2085 +vouched: 2026-11-12T19:44:00Z lines: 1-339 --- diff --git a/.lane/memory/package.json/01M6EGHDC3CRDZA07DMY7W1EHM-files-ships-native2-licenses.md b/.lane/memory/package.json/01M6EGHDC3CRDZA07DMY7W1EHM-files-ships-native2-licenses.md index bdf92ace..0d51e03f 100644 --- a/.lane/memory/package.json/01M6EGHDC3CRDZA07DMY7W1EHM-files-ships-native2-licenses.md +++ b/.lane/memory/package.json/01M6EGHDC3CRDZA07DMY7W1EHM-files-ships-native2-licenses.md @@ -4,8 +4,9 @@ anchor: '@file' created: 2026-11-02T16:12:36Z norm: '1' sig: 021fb596db81e6d0 -body_hash: b0d0a93ad9fd4411 -raw_hash: aba6b363e3981de3 +body_hash: 53e61ffa87279904 +raw_hash: 80d654e5bdd9cffa +vouched: 2026-11-12T19:44:00Z lines: 1-30 supersedes: 01M5R48RRWQMVESK6RVKFWF174 --- diff --git a/.lane/memory/src/tools.ts/01M6EGHDDEY51YSVQ7T6TS53SX-this-text-is-what-native2-se.md b/.lane/memory/src/tools.ts/01M6EGHDDEY51YSVQ7T6TS53SX-this-text-is-what-native2-se.md index 62bc3be4..b2f9d8d1 100644 --- a/.lane/memory/src/tools.ts/01M6EGHDDEY51YSVQ7T6TS53SX-this-text-is-what-native2-se.md +++ b/.lane/memory/src/tools.ts/01M6EGHDDEY51YSVQ7T6TS53SX-this-text-is-what-native2-se.md @@ -4,9 +4,9 @@ anchor: '@file' created: 2026-11-02T16:12:36Z norm: '1' sig: 78473a106add9cfc -body_hash: 20a6956cc14ca9d2 -raw_hash: 0fb3a60c49e77ca1 -vouched: 2026-11-03T02:45:55Z +body_hash: dee2458bb266d140 +raw_hash: 38e6dd2a0a1ad4b4 +vouched: 2026-11-12T19:44:00Z lines: 1-980 supersedes: 01M5D682W73BEYBRAPTSK672XW ---