feat(keyboard): ship AI hint carousel, home library cards, and clipboard polish

Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
This commit is contained in:
Rocky
2026-08-13 01:00:51 +08:00
parent fd6e0d3e7e
commit 9f308fadd2
202 changed files with 10897 additions and 5962 deletions
+294
View File
@@ -0,0 +1,294 @@
{
"originHash" : "3f7f9c5c939e016eed7645a0f0350b81159851de16e991f2d664636fd06245e3",
"pins" : [
{
"identity" : "async-http-client",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/async-http-client",
"state" : {
"revision" : "2fc4652fb4689eb24af10e55cabaa61d8ba774fd",
"version" : "1.32.0"
}
},
{
"identity" : "eventsource",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattt/EventSource.git",
"state" : {
"revision" : "a2965424a4babeb0c8e4b5ec9708c3939bc52449",
"version" : "1.2.0"
}
},
{
"identity" : "librime-xcframework",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ghostflyby/librime-xcframework.git",
"state" : {
"revision" : "d5781922905ff1ae967c222e40aa51cc451a5221",
"version" : "1.17.0-pack.1"
}
},
{
"identity" : "mlx-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift.git",
"state" : {
"revision" : "61b9e011e09a62b489f6bd647958f1555bdf2896",
"version" : "0.31.3"
}
},
{
"identity" : "mlx-swift-lm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ml-explore/mlx-swift-lm.git",
"state" : {
"revision" : "1c05248bb0899e2a7a4962b84d319cf12f4e12aa",
"version" : "3.31.3"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
"version" : "1.2.1"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "810496cf121e525d660cd0ea89a758740476b85f",
"version" : "1.5.1"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms.git",
"state" : {
"revision" : "2971dd5d9f6e0515664b01044826bcea16e59fac",
"version" : "1.1.2"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
"version" : "1.3.0"
}
},
{
"identity" : "swift-certificates",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-certificates.git",
"state" : {
"revision" : "24ccdeeeed4dfaae7955fcac9dbf5489ed4f1a25",
"version" : "1.18.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
"version" : "1.3.0"
}
},
{
"identity" : "swift-configuration",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-configuration.git",
"state" : {
"revision" : "1bb939fe7bbb00b8f8bab664cc90020c035c08d9",
"version" : "1.1.0"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095",
"version" : "4.2.0"
}
},
{
"identity" : "swift-distributed-tracing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-distributed-tracing.git",
"state" : {
"revision" : "e109d8b5308d0e05201d9a1dd1c475446a946a11",
"version" : "1.4.0"
}
},
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b",
"version" : "1.6.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types.git",
"state" : {
"revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
"version" : "1.5.1"
}
},
{
"identity" : "swift-huggingface",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-huggingface.git",
"state" : {
"revision" : "de01c0ab8fd537bbd8216cea7f774275178501a2",
"version" : "0.8.1"
}
},
{
"identity" : "swift-jinja",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-jinja.git",
"state" : {
"revision" : "f731f03bf746481d4fda07f817c3774390c4d5b9",
"version" : "2.3.2"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "bbd81b6725ae874c69e9b8c8804d462356b55523",
"version" : "1.10.1"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "e932d3c4d8f77433c8f7093b5ebcbf91463948a0",
"version" : "2.95.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "3df009d563dc9f21a5c85b33d8c2e34d2e4f8c3b",
"version" : "1.32.1"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "b6571f3db40799df5a7fc0e92c399aa71c883edd",
"version" : "1.40.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "173cc69a058623525a58ae6710e2f5727c663793",
"version" : "2.36.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "60c3e187154421171721c1a38e800b390680fb5d",
"version" : "1.26.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics",
"state" : {
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
"version" : "1.1.1"
}
},
{
"identity" : "swift-service-context",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-service-context.git",
"state" : {
"revision" : "d0997351b0c7779017f88e7a93bc30a1878d7f29",
"version" : "1.3.0"
}
},
{
"identity" : "swift-service-lifecycle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/swift-service-lifecycle",
"state" : {
"revision" : "89888196dd79c61c50bca9a103d8114f32e1e598",
"version" : "2.10.1"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax.git",
"state" : {
"revision" : "0687f71944021d616d34d922343dcef086855920",
"version" : "600.0.1"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
"version" : "1.6.4"
}
},
{
"identity" : "swift-transformers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huggingface/swift-transformers.git",
"state" : {
"revision" : "150169bfba0889c229a2ce7494cf8949f18e6906",
"version" : "1.1.9"
}
},
{
"identity" : "swift-xet",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mattt/swift-xet.git",
"state" : {
"revision" : "341bfd4172f6a57119bfd49bafa11cf5d21fab75",
"version" : "0.2.3"
}
},
{
"identity" : "yyjson",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ibireme/yyjson.git",
"state" : {
"revision" : "8b4a38dc994a110abaec8a400615567bd996105f",
"version" : "0.12.0"
}
}
],
"version" : 3
}
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
"""Reject obvious credential and user-text interpolation in Swift logs."""
from __future__ import annotations
import argparse
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
EXCLUDED_PARTS = {
".build",
".git",
"Carthage",
"DerivedData",
"OSGKeyboardExtTests",
"OSGKeyboardMacTests",
"OSGKeyboardTests",
"Pods",
"Scripts",
"Tests",
"ThirdParty",
"Vendor",
"build",
}
LOG_START = re.compile(
r"""
(?:
\bprint\s*\(
|\bOSGLog(?:\.\w+)+\s*\(
|\bOSGDiag\.log\s*\(
|\bFlowDiagnostics\.log\s*\(
|\bFlowTrace\.(?:capture|pipeline|asr|polish|keyboard|warn)\s*\(
|\bdebug\s*\(
|\b(?:log|logger)\.(?:debug|info|notice|warning|error|fault)\s*\(
)
""",
re.VERBOSE,
)
SENSITIVE_IDENTIFIER = re.compile(
r"""
\b(?:
apiKey
|asrApiKey
|Authorization
|httpBody
|bodyText
|responseBody
|result\.text
|error\.message
|(?:raw|final|full|partial)?Transcript
|(?:system|user|raw)?Prompt
|clipboard(?:Text|Content)?
)\b
""",
re.IGNORECASE | re.VERBOSE,
)
INTERPOLATION = re.compile(r"\\\((.*?)\)", re.DOTALL)
SENSITIVE_LABEL = re.compile(
r"\b(?:apiKey|Authorization|httpBody|bodyText|responseBody|transcript|prompt|clipboard)\s*[:=]",
re.IGNORECASE,
)
@dataclass(frozen=True)
class Violation:
path: Path
line: int
message: str
def swift_files(root: Path) -> list[Path]:
files: list[Path] = []
for current, directories, names in os.walk(root):
directories[:] = [
directory
for directory in directories
if directory not in EXCLUDED_PARTS
and not directory.endswith("Tests")
and not directory.startswith(".")
]
current_path = Path(current)
files.extend(current_path / name for name in names if name.endswith(".swift"))
return sorted(files)
def line_number(source: str, offset: int) -> int:
return source.count("\n", 0, offset) + 1
def extract_call(source: str, start: int) -> str:
depth = 0
saw_open = False
for index in range(start, min(len(source), start + 16_384)):
character = source[index]
if character == "(":
depth += 1
saw_open = True
elif character == ")" and saw_open:
depth -= 1
if depth == 0:
return source[start : index + 1]
return source[start : min(len(source), start + 16_384)]
def extract_transcript_function(source: str) -> tuple[int, str] | None:
match = re.search(r"\bfunc\s+transcript\s*\(", source)
if match is None:
return None
brace = source.find("{", match.end())
if brace < 0:
return None
depth = 0
for index in range(brace, len(source)):
if source[index] == "{":
depth += 1
elif source[index] == "}":
depth -= 1
if depth == 0:
return match.start(), source[brace : index + 1]
return match.start(), source[brace:]
def scan_file(path: Path) -> list[Violation]:
source = path.read_text(encoding="utf-8")
violations: list[Violation] = []
for match in LOG_START.finditer(source):
line_start = source.rfind("\n", 0, match.start()) + 1
if source[line_start : match.start()].lstrip().startswith("//"):
continue
call = extract_call(source, match.start())
expressions = INTERPOLATION.findall(call)
exposed_expressions = [
re.sub(r"\b(?:result\.text\?|error\.message)\.count\b", "", expression)
for expression in expressions
]
if any(SENSITIVE_IDENTIFIER.search(expression) for expression in exposed_expressions):
violations.append(
Violation(
path,
line_number(source, match.start()),
"sensitive identifier interpolated into a log",
)
)
continue
if SENSITIVE_LABEL.search(call) and (expressions or "+" in call):
violations.append(
Violation(
path,
line_number(source, match.start()),
"sensitive log label may expose user or credential text",
)
)
continue
opening = call.find("(")
direct_argument = call[opening + 1 :] if opening >= 0 else call
if SENSITIVE_IDENTIFIER.match(direct_argument.lstrip()):
violations.append(
Violation(
path,
line_number(source, match.start()),
"sensitive value passed directly to a log",
)
)
continue
if "CloudASR" in path.parts and re.search(
r"\\\([^)]*\.localizedDescription\b", call
):
violations.append(
Violation(
path,
line_number(source, match.start()),
"cloud ASR log exposes localized error detail",
)
)
if path.name == "FlowTrace.swift" or "enum FlowTrace" in source:
transcript_function = extract_transcript_function(source)
if transcript_function is not None:
offset, body = transcript_function
if re.search(r"\\\(\s*text\b", body):
violations.append(
Violation(
path,
line_number(source, offset),
"FlowTrace.transcript must not interpolate text",
)
)
return violations
def scan(paths: list[Path]) -> list[Violation]:
violations: list[Violation] = []
for path in paths:
violations.extend(scan_file(path))
return violations
def run_self_test() -> bool:
fixture_root = ROOT / "Scripts"
safe = fixture_root / "sensitive_logs_safe.fixture.swift"
unsafe = fixture_root / "sensitive_logs_unsafe.fixture.swift"
safe_violations = scan([safe])
unsafe_violations = scan([unsafe])
if safe_violations:
print("self-test failed: safe fixture was rejected", file=sys.stderr)
return False
if len(unsafe_violations) < 4:
print("self-test failed: unsafe fixture was not fully rejected", file=sys.stderr)
return False
print("Sensitive-log gate self-test passed.")
return True
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--self-test",
action="store_true",
help="verify the scanner against safe and unsafe fixtures",
)
args = parser.parse_args()
if args.self_test:
return 0 if run_self_test() else 1
violations = scan(swift_files(ROOT))
if violations:
for violation in violations:
relative = violation.path.relative_to(ROOT)
print(f"{relative}:{violation.line}: {violation.message}", file=sys.stderr)
print(f"Sensitive-log gate failed with {len(violations)} violation(s).", file=sys.stderr)
return 1
print("Sensitive-log gate passed.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+36 -12
View File
@@ -4,11 +4,21 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VENDOR="$ROOT/ThirdParty/mlx-audio-swift"
PATCH_MARKER="$VENDOR/.osg-context-patch-applied"
PATCH_MARKER="$VENDOR/.osg-context-patch-v2-applied"
PINNED_COMMIT="d302a5c6080d2bb97bae38c7418f82abb76013b6"
if [[ ! -f "$VENDOR/Package.swift" ]]; then
echo "Cloning mlx-audio-swift into ThirdParty/..."
git clone --depth 1 https://github.com/Blaizzy/mlx-audio-swift "$VENDOR"
echo "Cloning pinned mlx-audio-swift into ThirdParty/..."
git clone --filter=blob:none --no-checkout https://github.com/Blaizzy/mlx-audio-swift "$VENDOR"
git -C "$VENDOR" fetch --depth 1 origin "$PINNED_COMMIT"
git -C "$VENDOR" checkout --detach FETCH_HEAD
fi
ACTUAL_COMMIT="$(git -C "$VENDOR" rev-parse HEAD)"
if [[ "$ACTUAL_COMMIT" != "$PINNED_COMMIT" ]]; then
echo "error: mlx-audio-swift is at $ACTUAL_COMMIT; expected $PINNED_COMMIT" >&2
echo "Remove ThirdParty/mlx-audio-swift and rerun this script." >&2
exit 1
fi
if [[ -f "$PATCH_MARKER" ]]; then
@@ -32,7 +42,19 @@ if "mlx-swift.git\", exact:" not in mtext:
'.package(url: "https://github.com/ml-explore/mlx-swift.git", .upToNextMajor(from: "0.30.6")),',
'.package(url: "https://github.com/ml-explore/mlx-swift.git", exact: "0.31.3"),',
)
manifest.write_text(mtext)
mtext = mtext.replace(
'.package(url: "https://github.com/ml-explore/mlx-swift-lm.git", .upToNextMajor(from: "3.31.3")),',
'.package(url: "https://github.com/ml-explore/mlx-swift-lm.git", exact: "3.31.3"),',
)
mtext = mtext.replace(
'.package(url: "https://github.com/huggingface/swift-transformers.git", .upToNextMajor(from: "1.1.6")),',
'.package(url: "https://github.com/huggingface/swift-transformers.git", exact: "1.1.9"),',
)
mtext = mtext.replace(
'.package(url: "https://github.com/huggingface/swift-huggingface.git", .upToNextMajor(from: "0.8.1"))',
'.package(url: "https://github.com/huggingface/swift-huggingface.git", exact: "0.8.1")',
)
manifest.write_text(mtext)
text = types.read_text()
if "public var context: String?" not in text:
@@ -53,14 +75,16 @@ if "public var context: String?" not in text:
types.write_text(text)
text = session.read_text()
text = text.replace(
" language: params.config.language\n )",
" context: params.config.context ?? \"\",\n language: params.config.language\n )",
)
text = text.replace(
" language: config.language\n )",
" context: config.context ?? \"\",\n language: config.language\n )",
)
if "context: params.config.context" not in text:
text = text.replace(
" language: params.config.language\n )",
" context: params.config.context ?? \"\",\n language: params.config.language\n )",
)
if "context: config.context" not in text:
text = text.replace(
" language: config.language\n )",
" context: config.context ?? \"\",\n language: config.language\n )",
)
session.write_text(text)
PY
+9
View File
@@ -6,6 +6,8 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
PBXPROJ="$ROOT/OSGKeyboard.xcodeproj/project.pbxproj"
PACKAGE_LOCK="$ROOT/Scripts/Package.resolved.lock"
GENERATED_PACKAGE_LOCK="$ROOT/OSGKeyboard.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"
# XcodeGen requires configFiles listed in project.yml to exist on disk.
# Signing.local.xcconfig is gitignored so each machine keeps its own team ID.
@@ -25,6 +27,13 @@ fi
xcodegen generate
if [[ ! -f "$PACKAGE_LOCK" ]]; then
echo "error: missing reviewed SwiftPM lock at $PACKAGE_LOCK" >&2
exit 1
fi
mkdir -p "$(dirname "$GENERATED_PACKAGE_LOCK")"
cp "$PACKAGE_LOCK" "$GENERATED_PACKAGE_LOCK"
"$ROOT/Scripts/patch-spm-local-package.sh"
if python3 - "$PBXPROJ" <<'PY'
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Installs the reviewed XcodeGen release into RUNNER_TEMP for GitHub Actions.
set -euo pipefail
VERSION="2.43.0"
SHA256="a4847ed77d3341a4d24049bc4424a3babca4c94ff1dcaaee923eaca2b32c678f"
DESTINATION="${RUNNER_TEMP:?RUNNER_TEMP is required}/xcodegen-$VERSION"
ARCHIVE="$RUNNER_TEMP/xcodegen-$VERSION.zip"
curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 \
--connect-timeout 15 --max-time 120 \
"https://github.com/yonaskolb/XcodeGen/releases/download/$VERSION/xcodegen.zip" \
-o "$ARCHIVE"
echo "$SHA256 $ARCHIVE" | shasum -a 256 -c -
rm -rf "$DESTINATION"
mkdir -p "$DESTINATION"
unzip -q "$ARCHIVE" -d "$DESTINATION"
test -x "$DESTINATION/bin/xcodegen"
echo "$DESTINATION/bin" >> "$GITHUB_PATH"
"$DESTINATION/bin/xcodegen" --version
+7
View File
@@ -23,6 +23,8 @@ TEST_ROOTS = [
ROOT / "OSGKeyboardExtTests",
ROOT / "OSGKeyboardMacTests",
]
SHARED_TEST_ROOT = ROOT / "Tests"
SHARED_TEST_TARGETS = ["OSGKeyboardTests", "OSGKeyboardMacTests"]
def load_manifest() -> dict:
@@ -87,6 +89,11 @@ def discover_on_disk_test_classes() -> dict[str, Path]:
class_name = path.stem
test_id = f"{target}/{class_name}"
found[test_id] = path
if SHARED_TEST_ROOT.is_dir():
for path in sorted(SHARED_TEST_ROOT.glob("*Tests.swift")):
for target in SHARED_TEST_TARGETS:
test_id = f"{target}/{path.stem}"
found[test_id] = path
return found
+1
View File
@@ -134,6 +134,7 @@ run_xcodebuild() {
-scheme "$scheme"
-destination "$destination"
-configuration "$CONFIGURATION"
-onlyUsePackageVersionsFromResolvedFile
CODE_SIGNING_ALLOWED=NO
)
local test_id
+12
View File
@@ -0,0 +1,12 @@
import Foundation
func buildRequest(apiKey: String, payload: Data, provider: String, status: Int) {
var request = URLRequest(url: URL(string: "https://example.com/v1")!)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.httpBody = payload
print("provider=\(provider) status=\(status) responseBytes=\(payload.count)")
}
func traceSafely(transcript: String) {
FlowTrace.transcript("asr.final", transcript, "source=fixture")
}
@@ -0,0 +1,25 @@
func leakCredential(apiKey: String) {
print("credential=\(apiKey)")
}
func leakResponse(bodyText: String) {
OSGLog.flow.error("response=\(bodyText, privacy: .public)")
}
func leakPrompt(text: String) {
debug("prompt=\(text)")
}
func leakResult(result: FlowResult) {
FlowTrace.warn("failed", "message=\(result.text ?? "nil")")
}
func leakError(error: FlowTranscriptionError) {
debug("failure=\(error.message)")
}
enum FlowTrace {
static func transcript(_ step: String, _ text: String) {
print("stage=\(step) text=\(text)")
}
}