Ariver
2026-07-02 a8062bcb8cc69e08d0805ee5b2d93935613ceb60
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/usr/bin/env bash
set -euo pipefail
 
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
APP_GRID_SUPPORT_SWIFT="$ROOT_DIR/Apptag/AppGridSupport.swift"
CONTENT_VIEW_SWIFT="$ROOT_DIR/Apptag/ContentView.swift"
 
fail() {
  printf 'FAIL: %s\n' "$*" >&2
  exit 1
}
 
[[ -f "$APP_GRID_SUPPORT_SWIFT" ]] || fail "missing AppGridSupport.swift"
[[ -f "$CONTENT_VIEW_SWIFT" ]] || fail "missing ContentView.swift"
 
python3 - "$APP_GRID_SUPPORT_SWIFT" "$CONTENT_VIEW_SWIFT" <<'PY'
import pathlib
import re
import sys
 
support = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
content_view = pathlib.Path(sys.argv[2]).read_text(encoding="utf-8")
 
 
def fail(message: str) -> None:
    print(f"FAIL: {message}", file=sys.stderr)
    sys.exit(1)
 
 
def require(pattern: str, source: str, message: str) -> None:
    if not re.search(pattern, source, re.S):
        fail(message)
 
 
require(
    r"private\s+struct\s+AppBubbleNoteTextField\s*:\s*NSViewRepresentable",
    support,
    "App note bubble editor must use an AppKit-backed NSTextField bridge",
)
require(
    r"private\s+final\s+class\s+AppBubbleNativeTextField\s*:\s*NSTextField",
    support,
    "App note bubble editor must use a native NSTextField subclass",
)
require(
    r"AppBubbleNoteTextField\s*\((?P<body>.*?)text\s*:\s*\$draftNote(?P<body2>.*?)isFocused\s*:\s*noteFocused(?P<body3>.*?)onCommit\s*:\s*onCommit(?P<body4>.*?)onCancel\s*:\s*onCancel",
    support,
    "AppNameBubble must route draft text, focus, submit, and cancel through the native note editor",
)
 
if re.search(r"TextField\s*\(\s*tr\s*\(\s*\"appNote\.placeholder\"", support):
    fail("App note bubble must not use SwiftUI TextField inside the nonactivating overlay")
if "limitedDraft" in support:
    fail("App note bubble must not truncate draft text through a SwiftUI Binding setter")
 
require(
    r"func\s+updateNSView\s*\((?P<body>.*?)if\s+!field\.hasMarkedTextInput\s*,\s*field\.stringValue\s*!=\s*text",
    support,
    "updateNSView must not overwrite the native editor while IME marked text is active",
)
require(
    r"func\s+controlTextDidChange\s*\((?P<body>.*?)!field\.hasMarkedTextInput(?P<body2>.*?)syncFinalText\s*\(\s*from\s*:\s*field\s*\)",
    support,
    "controlTextDidChange must ignore interim IME marked text and only sync finalized text",
)
require(
    r"doCommandBy\s+commandSelector\s*:\s*Selector(?P<body>.*?)if\s+textView\.hasMarkedText\(\)\s*\{\s*return\s+false\s*\}",
    support,
    "IME command handling must stay with NSTextView while marked text is active",
)
require(
    r"case\s+#selector\s*\(\s*NSResponder\.insertNewline\(_:\)\s*\)(?P<body>.*?)syncFinalText\s*\(\s*from\s*:\s*field\s*\)(?P<body2>.*?)onCommit\(\)(?P<body3>.*?)return\s+true",
    support,
    "Return must commit only after final text is synced",
)
require(
    r"case\s+#selector\s*\(\s*NSResponder\.cancelOperation\(_:\)\s*\)(?P<body>.*?)onCancel\(\)(?P<body2>.*?)return\s+true",
    support,
    "Escape must retain the existing app note bubble cancel behavior after IME marked text is finalized",
)
require(
    r"@objc\s+func\s+submitAction\s*\((?P<body>.*?)!field\.hasMarkedTextInput(?P<body2>.*?)syncFinalText\s*\(\s*from\s*:\s*field\s*\)(?P<body3>.*?)onCommit\(\)",
    support,
    "NSTextField submit action must ignore marked text and commit finalized text only",
)
require(
    r"private\s+func\s+syncFinalText\s*\((?P<body>.*?)prefix\s*\(\s*TagDatabase\.maxAppNoteLength\s*\)(?P<body2>.*?)text\.wrappedValue\s*=\s*limited",
    support,
    "finalized note text must still be capped at TagDatabase.maxAppNoteLength",
)
require(
    r"min\s*\(\s*draftNote\.count\s*,\s*TagDatabase\.maxAppNoteLength\s*\)",
    support,
    "bubble character counter must not exceed the note length limit during interim input",
)
require(
    r"private\s+func\s+commitBubbleNote\s*\(\s*\)(?P<body>.*?)TagDatabase\.maxAppNoteLength",
    content_view,
    "commitBubbleNote must retain the final note length cap",
)
 
print("PASS app note bubble input QA: native NSTextField bridge, IME marked-text guards, and final note length cap are present")
PY