diff --git a/.github/workflows/release-readiness.yml b/.github/workflows/release-readiness.yml
index c5d7665..d0b967b 100644
--- a/.github/workflows/release-readiness.yml
+++ b/.github/workflows/release-readiness.yml
@@ -40,3 +40,36 @@ jobs:
xcrun simctl list runtimes
- run: ./Scripts/release-readiness.sh
- run: ./Scripts/ipad-icloud-tests.sh
+ - name: Report XCTest results
+ if: always()
+ run: |
+ python3 - <<'PY'
+ import glob
+ import json
+ import subprocess
+
+ for root in ("/tmp/scriptwidget-release-readiness", "/tmp/scriptwidget-ipad-icloud-tests"):
+ for path in sorted(glob.glob(f"{root}/*/Logs/Test/*.xcresult")):
+ print(f"XCTest result: {path}", flush=True)
+ result = subprocess.run(
+ ["xcrun", "xcresulttool", "get", "test-results", "summary", "--path", path],
+ capture_output=True, text=True,
+ )
+ if result.returncode:
+ print(result.stderr, flush=True)
+ continue
+ summary = json.loads(result.stdout)
+ print(json.dumps({key: summary.get(key) for key in (
+ "title", "result", "passedTests", "failedTests", "skippedTests", "testFailures"
+ )}, indent=2), flush=True)
+ PY
+ - name: Upload XCTest diagnostics
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: xctest-results-${{ github.run_attempt }}
+ path: |
+ /tmp/scriptwidget-release-readiness/*/Logs/Test/
+ /tmp/scriptwidget-ipad-icloud-tests/*/Logs/Test/
+ if-no-files-found: warn
+ retention-days: 7
diff --git a/Scripts/macos-widget-link-tests.sh b/Scripts/macos-widget-link-tests.sh
new file mode 100644
index 0000000..3b2b6ca
--- /dev/null
+++ b/Scripts/macos-widget-link-tests.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+set -eu
+
+ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
+TEST_DIR=$(mktemp -d /tmp/scriptwidget-link-tests.XXXXXX)
+trap 'rm -rf "$TEST_DIR"' EXIT
+
+swiftc -module-cache-path "$TEST_DIR/module-cache" \
+ "$ROOT/macOS/ScriptWidgetMac/App/AppDelegate.swift" \
+ "$ROOT/Shared/ScriptWidgetRuntime/Widget/Runtime/DeepLinkDefine.swift" \
+ "$ROOT/Tests/MacWidgetLinkTests/main.swift" \
+ -o "$TEST_DIR/widget-link-tests"
+"$TEST_DIR/widget-link-tests"
diff --git a/Scripts/release-readiness.sh b/Scripts/release-readiness.sh
index 4b246fc..b2eeb3a 100755
--- a/Scripts/release-readiness.sh
+++ b/Scripts/release-readiness.sh
@@ -13,6 +13,7 @@ git diff --exit-code -- \
macOS/ScriptWidgetMac/StudioEditor.bundle
if [ "${SCRIPTWIDGET_SKIP_XCODE:-0}" != "1" ]; then
+ sh Scripts/macos-widget-link-tests.sh
xcodebuild -quiet -project macOS/ScriptWidgetMac.xcodeproj -scheme ScriptWidgetRuntimeTests \
-destination 'platform=macOS' -derivedDataPath "$DERIVED_DATA/macos-tests" \
CODE_SIGNING_ALLOWED=NO test
diff --git a/Tests/MacWidgetLinkTests/main.swift b/Tests/MacWidgetLinkTests/main.swift
new file mode 100644
index 0000000..22cb368
--- /dev/null
+++ b/Tests/MacWidgetLinkTests/main.swift
@@ -0,0 +1,32 @@
+import AppKit
+import Foundation
+
+// Isolate the app delegate from iCloud caching for this routing regression test.
+final class StubScriptManager {
+ func precacheAllScripts() {}
+}
+let sharedScriptManager = StubScriptManager()
+
+let delegate = AppDelegate()
+var openedURLs: [URL] = []
+delegate.openWidgetURL = { openedURLs.append($0) }
+
+let externalURLs = [
+ URL(string: "https://xnu.app/scriptwidget")!,
+ URL(string: "scriptable:///run/Name")!,
+ URL(fileURLWithPath: "/tmp/widget-card.html"),
+ URL(string: "mailto:example@example.com")!
+]
+let internalURLs = [
+ kDeepLinkDefaultURL,
+ URL(string: "WIDGET-DEEPLINK://default")!,
+ URL(string: "scriptwidget://open")!,
+ URL(string: "SCRIPTWIDGET://open")!,
+ URL(string: "relative/path")!
+]
+
+delegate.application(NSApplication.shared, open: internalURLs + externalURLs)
+precondition(openedURLs == externalURLs, "Forward external URLs in order and ignore internal/schemeless URLs")
+delegate.application(NSApplication.shared, open: [])
+precondition(openedURLs == externalURLs, "An empty URL batch should do nothing")
+print("✓ macOS widget URL routing passed")
diff --git a/Tests/ScriptWidgetRuntimeTests/RuntimeExecutionTests.swift b/Tests/ScriptWidgetRuntimeTests/RuntimeExecutionTests.swift
index 6051f00..f414258 100644
--- a/Tests/ScriptWidgetRuntimeTests/RuntimeExecutionTests.swift
+++ b/Tests/ScriptWidgetRuntimeTests/RuntimeExecutionTests.swift
@@ -260,7 +260,16 @@ final class RuntimeExecutionTests: XCTestCase {
}
}
- func testConcurrentRuntimesKeepEnvironmentStateIsolated() {
+ func testConcurrentRuntimesKeepEnvironmentStateIsolated() throws {
+ let source = "$render({$getenv(\"widget-param\")});"
+ // Exercise concurrent environment isolation without racing eight cold
+ // Babel compilations of identical source on constrained CI simulators.
+ // Each concurrent execution still gets its own package and JSContext.
+ let warmup = makeRuntime(environments: ["widget-size": "medium", "widget-param": "warmup"])
+ .executeJSXSyncForWidget(source)
+ XCTAssertNil(warmup.1)
+ XCTAssertEqual(collectText(try XCTUnwrap(warmup.0)), "warmup")
+
let lock = NSLock()
var rendered: [Int: String] = [:]
var failures: [String] = []
@@ -268,9 +277,7 @@ final class RuntimeExecutionTests: XCTestCase {
DispatchQueue.concurrentPerform(iterations: 8) { index in
let token = "runtime-\(index)"
let runtime = makeRuntime(environments: ["widget-size": "medium", "widget-param": token])
- let (element, error) = runtime.executeJSXSyncForWidget(
- "$render({$getenv(\"widget-param\")});"
- )
+ let (element, error) = runtime.executeJSXSyncForWidget(source)
lock.lock()
defer { lock.unlock() }
if let error {
diff --git a/macOS/ScriptWidgetMac/App/AppDelegate.swift b/macOS/ScriptWidgetMac/App/AppDelegate.swift
index faa64df..546c208 100644
--- a/macOS/ScriptWidgetMac/App/AppDelegate.swift
+++ b/macOS/ScriptWidgetMac/App/AppDelegate.swift
@@ -9,6 +9,19 @@ import Foundation
import AppKit
class AppDelegate: NSObject, NSApplicationDelegate {
+ var openWidgetURL: (URL) -> Void = { url in
+ NSWorkspace.shared.open(url)
+ }
+
+ func application(_ application: NSApplication, open urls: [URL]) {
+ for url in urls {
+ guard let scheme = url.scheme?.lowercased(),
+ scheme != kDeepLinkDefaultScheme,
+ scheme != "scriptwidget" else { continue }
+ openWidgetURL(url)
+ }
+ }
+
func applicationDidFinishLaunching(_ notification: Notification) {
print("did finish launching")
// The embedded editor is served via a WKURLSchemeHandler — no
diff --git a/macOS/ScriptWidgetMac/Create/CreateGuideView.swift b/macOS/ScriptWidgetMac/Create/CreateGuideView.swift
index 80c350b..884417c 100644
--- a/macOS/ScriptWidgetMac/Create/CreateGuideView.swift
+++ b/macOS/ScriptWidgetMac/Create/CreateGuideView.swift
@@ -136,14 +136,10 @@ struct CreateGuideView: View {
}
} label: {
HStack(spacing: 12) {
- Image(systemName: "sparkles")
- .font(.title)
- .foregroundStyle(.white)
+ Image(systemName: "sparkle")
+ .font(.system(size: 32, weight: .light))
+ .foregroundStyle(.primary)
.frame(width: 44, height: 44)
- .background(LinearGradient(colors: [.purple, .blue],
- startPoint: .topLeading,
- endPoint: .bottomTrailing))
- .clipShape(RoundedRectangle(cornerRadius: 10))
VStack(alignment: .leading, spacing: 2) {
Text("Generate with AI").font(.headline)
Text("Describe your widget and let the AI build it.")