#!/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
|