#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" QUICK_SEARCH="$ROOT_DIR/Apptag/QuickSearch.swift" CONTENT_VIEW="$ROOT_DIR/Apptag/ContentView.swift" TAG_NAV="$ROOT_DIR/Apptag/TagNavigationView.swift" python3 - "$QUICK_SEARCH" "$CONTENT_VIEW" "$TAG_NAV" <<'PY' import re import sys from pathlib import Path quick_search = Path(sys.argv[1]).read_text(encoding="utf-8") content_view = Path(sys.argv[2]).read_text(encoding="utf-8") tag_nav = Path(sys.argv[3]).read_text(encoding="utf-8") def fail(message: str) -> None: print(f"FAIL: {message}", file=sys.stderr) sys.exit(1) def require(condition: bool, message: str) -> None: if not condition: fail(message) def require_contains(source: str, needle: str, message: str) -> None: require(needle in source, message) require_contains( quick_search, "tagLauncherQuickSearchCompositionChanged", "Quick Search must publish IME composition state", ) require_contains( quick_search, "if !field.isComposingText, field.stringValue != text", "Quick Search updateNSView must not overwrite marked text", ) require_contains( quick_search, "if textView.hasMarkedText()", "Quick Search doCommandBy must pass marked text commands through to IME", ) require_contains( quick_search, "guard !isComposingText else", "Quick Search keyDown must pass marked text key events through to IME", ) require_contains( quick_search, "guard field?.isComposingText != true else { return }", "Quick Search submit fallback must not launch while composing text", ) apply_match = re.search( r"func apply\(_ configuration: QuickSearchPanelConfiguration, anchorView: QuickSearchPanelAnchorView\).*?func closePanel\(\)", quick_search, re.S, ) require(apply_match is not None, "Quick Search panel apply block is missing") apply_body = apply_match.group(0) require_contains( apply_body, "let shouldOrderForInput = shouldOrderPanelForInput(panel)", "Quick Search panel must compute whether ordering is actually needed", ) require_contains( apply_body, "if shouldOrderForInput", "Quick Search panel activate/orderFront must be gated", ) require_contains( quick_search, "field.currentEditor() == nil", "Quick Search focus retry must stop once the field editor is active", ) require_contains( content_view, "@State private var quickSearchCompositionActive = false", "ContentView must track Quick Search composition state", ) require_contains( content_view, "shouldIgnoreQuickSearchDismissDuringComposition", "ContentView must ignore mouse/backdrop dismiss while composing text", ) require_contains( content_view, "source == QuickSearchDismissSource.mouse || source == QuickSearchDismissSource.backdrop", "composition dismiss guard must be scoped to mouse/backdrop dismissals", ) mouse_match = re.search( r"override func mouseDown\(with event: NSEvent\).*?@objc private func performActivation", tag_nav, re.S, ) require(mouse_match is not None, "TagNavigation mouseDown block is missing") mouse_body = mouse_match.group(0) for needle, message in [ ("let maxTrackingDuration: TimeInterval = 8", "TagNavigation tracking loop must have a hard time limit"), ("window.isVisible", "TagNavigation tracking loop must exit when the window is gone or hidden"), ("NSApp.isActive", "TagNavigation tracking loop must exit when the app loses active state"), ("NSEvent.pressedMouseButtons & (1 << 0) == 0", "TagNavigation tracking loop must recover if mouseUp is lost"), ("onReorderEnded()", "TagNavigation abnormal exits must clean up an active reorder"), ]: require_contains(mouse_body, needle, message) print("PASS windowserver event safety QA") PY