@hasna/recordings 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -69
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1919 -98
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1973 -236
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
umask 077
|
|
4
|
+
|
|
5
|
+
if [ "$#" -ne 2 ]; then
|
|
6
|
+
echo "Usage: $0 <Recordings.app> <explicit absolute Bun executable path>" >&2
|
|
7
|
+
exit 2
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
HOST_UNAME_EXECUTABLE="/usr/bin/uname"
|
|
11
|
+
SYSTEM_DIRNAME_EXECUTABLE="/usr/bin/dirname"
|
|
12
|
+
SYSTEM_PWD_EXECUTABLE="/bin/pwd"
|
|
13
|
+
SANITIZED_PATH="/usr/bin:/bin:/usr/sbin:/sbin"
|
|
14
|
+
|
|
15
|
+
require_executable() {
|
|
16
|
+
local label="$1"
|
|
17
|
+
local executable="$2"
|
|
18
|
+
case "$executable" in
|
|
19
|
+
/*) ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "$label must be configured as an absolute executable path." >&2
|
|
22
|
+
exit 1
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
if [ ! -f "$executable" ] || [ ! -x "$executable" ]; then
|
|
26
|
+
echo "$label is missing or is not an executable file: $executable" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
require_executable "HOST_UNAME_EXECUTABLE" "$HOST_UNAME_EXECUTABLE"
|
|
32
|
+
require_executable "SYSTEM_DIRNAME_EXECUTABLE" "$SYSTEM_DIRNAME_EXECUTABLE"
|
|
33
|
+
require_executable "SYSTEM_PWD_EXECUTABLE" "$SYSTEM_PWD_EXECUTABLE"
|
|
34
|
+
HOST_PLATFORM="$("$HOST_UNAME_EXECUTABLE" -s)"
|
|
35
|
+
readonly HOST_PLATFORM
|
|
36
|
+
|
|
37
|
+
if [ "$HOST_PLATFORM" != "Darwin" ] && [ "${RECORDINGS_TEST_SMOKE_ALLOW_NON_DARWIN:-0}" != "1" ]; then
|
|
38
|
+
echo "Recordings.app runtime smoke is only supported on macOS." >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
select_executable() {
|
|
43
|
+
local system_executable="$1"
|
|
44
|
+
local test_override="${2:-}"
|
|
45
|
+
if [ "$HOST_PLATFORM" = "Darwin" ] || [ -z "$test_override" ]; then
|
|
46
|
+
printf '%s\n' "$system_executable"
|
|
47
|
+
else
|
|
48
|
+
printf '%s\n' "$test_override"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
DIRNAME_EXECUTABLE="$(select_executable "$SYSTEM_DIRNAME_EXECUTABLE" "${RECORDINGS_TEST_SMOKE_DIRNAME_EXECUTABLE:-}")"
|
|
53
|
+
PWD_EXECUTABLE="$(select_executable "$SYSTEM_PWD_EXECUTABLE" "${RECORDINGS_TEST_SMOKE_PWD_EXECUTABLE:-}")"
|
|
54
|
+
BASENAME_EXECUTABLE="$(select_executable "/usr/bin/basename" "${RECORDINGS_TEST_SMOKE_BASENAME_EXECUTABLE:-}")"
|
|
55
|
+
ENV_EXECUTABLE="$(select_executable "/usr/bin/env" "${RECORDINGS_TEST_SMOKE_ENV_EXECUTABLE:-}")"
|
|
56
|
+
LSOF_EXECUTABLE="$(select_executable "/usr/sbin/lsof" "${RECORDINGS_TEST_SMOKE_LSOF_EXECUTABLE:-}")"
|
|
57
|
+
KILL_EXECUTABLE="$(select_executable "/bin/kill" "${RECORDINGS_TEST_SMOKE_KILL_EXECUTABLE:-}")"
|
|
58
|
+
MKTEMP_EXECUTABLE="$(select_executable "/usr/bin/mktemp" "${RECORDINGS_TEST_SMOKE_MKTEMP_EXECUTABLE:-}")"
|
|
59
|
+
MV_EXECUTABLE="$(select_executable "/bin/mv" "${RECORDINGS_TEST_SMOKE_MV_EXECUTABLE:-}")"
|
|
60
|
+
OPEN_EXECUTABLE="$(select_executable "/usr/bin/open" "${RECORDINGS_TEST_SMOKE_OPEN_EXECUTABLE:-}")"
|
|
61
|
+
PS_EXECUTABLE="$(select_executable "/bin/ps" "${RECORDINGS_TEST_SMOKE_PS_EXECUTABLE:-}")"
|
|
62
|
+
RM_EXECUTABLE="$(select_executable "/bin/rm" "${RECORDINGS_TEST_SMOKE_RM_EXECUTABLE:-}")"
|
|
63
|
+
SED_EXECUTABLE="$(select_executable "/usr/bin/sed" "${RECORDINGS_TEST_SMOKE_SED_EXECUTABLE:-}")"
|
|
64
|
+
SLEEP_EXECUTABLE="$(select_executable "/bin/sleep" "${RECORDINGS_TEST_SMOKE_SLEEP_EXECUTABLE:-}")"
|
|
65
|
+
TR_EXECUTABLE="$(select_executable "/usr/bin/tr" "${RECORDINGS_TEST_SMOKE_TR_EXECUTABLE:-}")"
|
|
66
|
+
|
|
67
|
+
for executable_spec in \
|
|
68
|
+
"DIRNAME_EXECUTABLE:$DIRNAME_EXECUTABLE" \
|
|
69
|
+
"PWD_EXECUTABLE:$PWD_EXECUTABLE" \
|
|
70
|
+
"BASENAME_EXECUTABLE:$BASENAME_EXECUTABLE" \
|
|
71
|
+
"ENV_EXECUTABLE:$ENV_EXECUTABLE" \
|
|
72
|
+
"LSOF_EXECUTABLE:$LSOF_EXECUTABLE" \
|
|
73
|
+
"KILL_EXECUTABLE:$KILL_EXECUTABLE" \
|
|
74
|
+
"MKTEMP_EXECUTABLE:$MKTEMP_EXECUTABLE" \
|
|
75
|
+
"MV_EXECUTABLE:$MV_EXECUTABLE" \
|
|
76
|
+
"OPEN_EXECUTABLE:$OPEN_EXECUTABLE" \
|
|
77
|
+
"PS_EXECUTABLE:$PS_EXECUTABLE" \
|
|
78
|
+
"RM_EXECUTABLE:$RM_EXECUTABLE" \
|
|
79
|
+
"SED_EXECUTABLE:$SED_EXECUTABLE" \
|
|
80
|
+
"SLEEP_EXECUTABLE:$SLEEP_EXECUTABLE" \
|
|
81
|
+
"TR_EXECUTABLE:$TR_EXECUTABLE"; do
|
|
82
|
+
require_executable "${executable_spec%%:*}" "${executable_spec#*:}"
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
BUN_EXECUTABLE="$2"
|
|
86
|
+
require_bun_executable() {
|
|
87
|
+
local executable="$1"
|
|
88
|
+
require_executable "BUN_EXECUTABLE" "$executable"
|
|
89
|
+
if ! "$ENV_EXECUTABLE" -i \
|
|
90
|
+
HOME="/tmp" \
|
|
91
|
+
PATH="$SANITIZED_PATH" \
|
|
92
|
+
"$executable" -e '
|
|
93
|
+
import { realpathSync, statSync } from "node:fs";
|
|
94
|
+
const expected = process.argv[1];
|
|
95
|
+
const actual = process.execPath;
|
|
96
|
+
if (!expected || realpathSync(actual) !== realpathSync(expected)) process.exit(66);
|
|
97
|
+
if (!statSync(actual).isFile()) process.exit(66);
|
|
98
|
+
if (!/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(Bun.version)) process.exit(66);
|
|
99
|
+
' "$executable"; then
|
|
100
|
+
echo "BUN_EXECUTABLE did not identify the Bun executable that actually ran." >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
require_bun_executable "$BUN_EXECUTABLE"
|
|
105
|
+
|
|
106
|
+
HOME_DIRECTORY="${HOME:-}"
|
|
107
|
+
case "$HOME_DIRECTORY" in
|
|
108
|
+
/*) ;;
|
|
109
|
+
*)
|
|
110
|
+
echo "Recordings.app runtime smoke requires an absolute HOME path." >&2
|
|
111
|
+
exit 1
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
if [ ! -d "$HOME_DIRECTORY" ]; then
|
|
115
|
+
echo "Recordings.app runtime smoke HOME is not a directory: $HOME_DIRECTORY" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
APP_PARENT="$(cd -P "$("$DIRNAME_EXECUTABLE" "$1")" && "$PWD_EXECUTABLE" -P)"
|
|
120
|
+
APP_PATH="$APP_PARENT/$("$BASENAME_EXECUTABLE" "$1")"
|
|
121
|
+
EXECUTABLE="$APP_PATH/Contents/MacOS/Recordings"
|
|
122
|
+
if [ ! -x "$EXECUTABLE" ]; then
|
|
123
|
+
echo "Recordings.app runtime smoke executable is missing: $EXECUTABLE" >&2
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
WORK_DIR="$($MKTEMP_EXECUTABLE -d /tmp/recordings-runtime-smoke.XXXXXX)"
|
|
128
|
+
SMOKE_MAX_ATTEMPTS=100
|
|
129
|
+
SMOKE_COMPLETION_ATTEMPTS=200
|
|
130
|
+
SMOKE_CLEANUP_ATTEMPTS=20
|
|
131
|
+
SMOKE_CLEANUP_BIND_ATTEMPTS=3
|
|
132
|
+
SMOKE_FOCUS_EVIDENCE="strict"
|
|
133
|
+
if [ -n "${SSH_CONNECTION:-}" ]; then
|
|
134
|
+
SMOKE_FOCUS_EVIDENCE="ssh-unavailable"
|
|
135
|
+
fi
|
|
136
|
+
SMOKE_PID=""
|
|
137
|
+
SMOKE_PID_START_IDENTITY=""
|
|
138
|
+
SMOKE_APP_PID=""
|
|
139
|
+
SMOKE_APP_PID_START_IDENTITY=""
|
|
140
|
+
SMOKE_APP_IDENTITIES_BEFORE_LAUNCH=""
|
|
141
|
+
SMOKE_ACK_PATH=""
|
|
142
|
+
|
|
143
|
+
process_start_identity() {
|
|
144
|
+
local pid="$1"
|
|
145
|
+
"$PS_EXECUTABLE" -o lstart= -p "$pid" 2>/dev/null | "$TR_EXECUTABLE" -d '\n'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
process_has_exact_executable() {
|
|
149
|
+
local pid="$1"
|
|
150
|
+
local expected_executable="$2"
|
|
151
|
+
local observed_executable
|
|
152
|
+
while IFS= read -r observed_executable; do
|
|
153
|
+
[ "$observed_executable" = "$expected_executable" ] && return 0
|
|
154
|
+
done < <(
|
|
155
|
+
"$LSOF_EXECUTABLE" -a -p "$pid" -d txt -Fn 2>/dev/null |
|
|
156
|
+
"$SED_EXECUTABLE" -n 's/^n//p'
|
|
157
|
+
)
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
capture_process_start_identity() {
|
|
162
|
+
local pid="$1"
|
|
163
|
+
local expected_executable="$2"
|
|
164
|
+
local start_before
|
|
165
|
+
local start_after
|
|
166
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
167
|
+
start_before="$(process_start_identity "$pid")"
|
|
168
|
+
[ -n "$start_before" ] || return 1
|
|
169
|
+
process_has_exact_executable "$pid" "$expected_executable" || return 1
|
|
170
|
+
start_after="$(process_start_identity "$pid")"
|
|
171
|
+
[ -n "$start_after" ] && [ "$start_before" = "$start_after" ] || return 1
|
|
172
|
+
printf '%s\n' "$start_before"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process_records_for_exact_executable() {
|
|
176
|
+
local expected_executable="$1"
|
|
177
|
+
local required_argument="${2:-}"
|
|
178
|
+
local process_listing
|
|
179
|
+
local pid
|
|
180
|
+
local command
|
|
181
|
+
local start_identity
|
|
182
|
+
if ! process_listing="$("$PS_EXECUTABLE" -axo pid=,command= -ww 2>/dev/null)"; then
|
|
183
|
+
return 1
|
|
184
|
+
fi
|
|
185
|
+
while read -r pid command; do
|
|
186
|
+
[[ "${pid:-}" =~ ^[1-9][0-9]*$ ]] || continue
|
|
187
|
+
case "$command" in
|
|
188
|
+
"$expected_executable"|"$expected_executable "*)
|
|
189
|
+
if [ -n "$required_argument" ]; then
|
|
190
|
+
case " $command " in
|
|
191
|
+
*" $required_argument "*) ;;
|
|
192
|
+
*) continue ;;
|
|
193
|
+
esac
|
|
194
|
+
fi
|
|
195
|
+
if start_identity="$(capture_process_start_identity "$pid" "$expected_executable")"; then
|
|
196
|
+
printf '%s\t%s\n' "$pid" "$start_identity"
|
|
197
|
+
fi
|
|
198
|
+
;;
|
|
199
|
+
esac
|
|
200
|
+
done <<< "$process_listing"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
process_identity_existed_before_launch() {
|
|
204
|
+
local candidate_pid="$1"
|
|
205
|
+
local candidate_start_identity="$2"
|
|
206
|
+
local pid
|
|
207
|
+
local start_identity
|
|
208
|
+
while IFS=$'\t' read -r pid start_identity; do
|
|
209
|
+
if [ "$pid" = "$candidate_pid" ] && [ "$start_identity" = "$candidate_start_identity" ]; then
|
|
210
|
+
return 0
|
|
211
|
+
fi
|
|
212
|
+
done <<< "$SMOKE_APP_IDENTITIES_BEFORE_LAUNCH"
|
|
213
|
+
return 1
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
bind_new_exact_app_process() {
|
|
217
|
+
local current_records
|
|
218
|
+
local pid
|
|
219
|
+
local start_identity
|
|
220
|
+
local candidate_pid=""
|
|
221
|
+
local candidate_start_identity=""
|
|
222
|
+
local candidate_count=0
|
|
223
|
+
[ -z "$SMOKE_APP_PID" ] || return 0
|
|
224
|
+
current_records="$(
|
|
225
|
+
process_records_for_exact_executable "$EXECUTABLE" "$SMOKE_ACK_PATH"
|
|
226
|
+
)" || return 1
|
|
227
|
+
while IFS=$'\t' read -r pid start_identity; do
|
|
228
|
+
[ -n "$pid" ] || continue
|
|
229
|
+
process_identity_existed_before_launch "$pid" "$start_identity" && continue
|
|
230
|
+
candidate_pid="$pid"
|
|
231
|
+
candidate_start_identity="$start_identity"
|
|
232
|
+
candidate_count=$((candidate_count + 1))
|
|
233
|
+
done <<< "$current_records"
|
|
234
|
+
[ "$candidate_count" -eq 1 ] || return 1
|
|
235
|
+
SMOKE_APP_PID="$candidate_pid"
|
|
236
|
+
SMOKE_APP_PID_START_IDENTITY="$candidate_start_identity"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
write_atomic_text_file() {
|
|
240
|
+
local destination="$1"
|
|
241
|
+
local contents="$2"
|
|
242
|
+
local temporary="${destination}.tmp.$$"
|
|
243
|
+
printf '%s\n' "$contents" > "$temporary"
|
|
244
|
+
"$MV_EXECUTABLE" -f "$temporary" "$destination"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
wait_for_process_exit() {
|
|
248
|
+
local pid="$1"
|
|
249
|
+
local attempts="$2"
|
|
250
|
+
local attempt
|
|
251
|
+
for ((attempt = 0; attempt < attempts; attempt++)); do
|
|
252
|
+
kill -0 "$pid" 2>/dev/null || return 0
|
|
253
|
+
"$SLEEP_EXECUTABLE" 0.1
|
|
254
|
+
done
|
|
255
|
+
! kill -0 "$pid" 2>/dev/null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
terminate_verified_process() {
|
|
259
|
+
local label="$1"
|
|
260
|
+
local pid="$2"
|
|
261
|
+
local expected_executable="$3"
|
|
262
|
+
local expected_start_identity="$4"
|
|
263
|
+
local rechecked_start_identity
|
|
264
|
+
|
|
265
|
+
kill -0 "$pid" 2>/dev/null || return 0
|
|
266
|
+
if ! rechecked_start_identity="$(capture_process_start_identity "$pid" "$expected_executable")" ||
|
|
267
|
+
[ "$rechecked_start_identity" != "$expected_start_identity" ]; then
|
|
268
|
+
echo "Refusing to signal ${label}: its process identity changed." >&2
|
|
269
|
+
return 1
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
"$KILL_EXECUTABLE" -TERM "$pid" 2>/dev/null || true
|
|
273
|
+
wait_for_process_exit "$pid" "$SMOKE_CLEANUP_ATTEMPTS" && return 0
|
|
274
|
+
|
|
275
|
+
if ! rechecked_start_identity="$(capture_process_start_identity "$pid" "$expected_executable")" ||
|
|
276
|
+
[ "$rechecked_start_identity" != "$expected_start_identity" ]; then
|
|
277
|
+
echo "Refusing to force-terminate ${label}: its process identity changed." >&2
|
|
278
|
+
return 1
|
|
279
|
+
fi
|
|
280
|
+
"$KILL_EXECUTABLE" -KILL "$pid" 2>/dev/null || true
|
|
281
|
+
if ! wait_for_process_exit "$pid" "$SMOKE_CLEANUP_ATTEMPTS"; then
|
|
282
|
+
echo "Could not reap verified ${label} process ${pid}." >&2
|
|
283
|
+
return 1
|
|
284
|
+
fi
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
cleanup() {
|
|
288
|
+
local exit_status=$?
|
|
289
|
+
local binding_attempt
|
|
290
|
+
trap - EXIT
|
|
291
|
+
if [ -n "$SMOKE_ACK_PATH" ] && [ ! -e "$SMOKE_ACK_PATH" ]; then
|
|
292
|
+
write_atomic_text_file "$SMOKE_ACK_PATH" "cleanup-$$" 2>/dev/null || true
|
|
293
|
+
fi
|
|
294
|
+
if [ -n "$SMOKE_PID" ]; then
|
|
295
|
+
wait_for_process_exit "$SMOKE_PID" "$SMOKE_CLEANUP_ATTEMPTS" || true
|
|
296
|
+
if [ -z "$SMOKE_APP_PID" ]; then
|
|
297
|
+
for ((binding_attempt = 0; binding_attempt < SMOKE_CLEANUP_BIND_ATTEMPTS; binding_attempt++)); do
|
|
298
|
+
bind_new_exact_app_process && break
|
|
299
|
+
"$SLEEP_EXECUTABLE" 0.1
|
|
300
|
+
done
|
|
301
|
+
fi
|
|
302
|
+
if [ -n "$SMOKE_APP_PID" ] && [ -n "$SMOKE_APP_PID_START_IDENTITY" ]; then
|
|
303
|
+
terminate_verified_process \
|
|
304
|
+
"Recordings.app" \
|
|
305
|
+
"$SMOKE_APP_PID" \
|
|
306
|
+
"$EXECUTABLE" \
|
|
307
|
+
"$SMOKE_APP_PID_START_IDENTITY" || true
|
|
308
|
+
fi
|
|
309
|
+
if ! wait_for_process_exit "$SMOKE_PID" "$SMOKE_CLEANUP_ATTEMPTS" &&
|
|
310
|
+
[ -n "$SMOKE_PID_START_IDENTITY" ]; then
|
|
311
|
+
terminate_verified_process \
|
|
312
|
+
"open -W wrapper" \
|
|
313
|
+
"$SMOKE_PID" \
|
|
314
|
+
"$OPEN_EXECUTABLE" \
|
|
315
|
+
"$SMOKE_PID_START_IDENTITY" || true
|
|
316
|
+
fi
|
|
317
|
+
fi
|
|
318
|
+
if [ -n "$SMOKE_PID" ] && ! kill -0 "$SMOKE_PID" 2>/dev/null; then
|
|
319
|
+
wait "$SMOKE_PID" 2>/dev/null || true
|
|
320
|
+
fi
|
|
321
|
+
"$RM_EXECUTABLE" -rf "$WORK_DIR"
|
|
322
|
+
return "$exit_status"
|
|
323
|
+
}
|
|
324
|
+
trap cleanup EXIT
|
|
325
|
+
|
|
326
|
+
run_smoke() {
|
|
327
|
+
local mode="$1"
|
|
328
|
+
local output="$WORK_DIR/${mode}.json"
|
|
329
|
+
local acknowledgement="$WORK_DIR/${mode}.ack"
|
|
330
|
+
local completion="$WORK_DIR/${mode}.completion.json"
|
|
331
|
+
local log="$WORK_DIR/${mode}.log"
|
|
332
|
+
local attempt
|
|
333
|
+
local challenge
|
|
334
|
+
local completion_attempt
|
|
335
|
+
local rechecked_start_identity
|
|
336
|
+
local result_pid
|
|
337
|
+
local result_pid_start_identity
|
|
338
|
+
local wrapper_status
|
|
339
|
+
local -a arguments=(
|
|
340
|
+
--runtime-smoke "$mode"
|
|
341
|
+
--runtime-smoke-output "$output"
|
|
342
|
+
--runtime-smoke-ack "$acknowledgement"
|
|
343
|
+
--runtime-smoke-completion "$completion"
|
|
344
|
+
)
|
|
345
|
+
if [ "$mode" = "permission-helper" ]; then
|
|
346
|
+
arguments=(--request-permissions "${arguments[@]}")
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
SMOKE_PID=""
|
|
350
|
+
SMOKE_PID_START_IDENTITY=""
|
|
351
|
+
SMOKE_APP_PID=""
|
|
352
|
+
SMOKE_APP_PID_START_IDENTITY=""
|
|
353
|
+
SMOKE_APP_IDENTITIES_BEFORE_LAUNCH=""
|
|
354
|
+
SMOKE_ACK_PATH="$acknowledgement"
|
|
355
|
+
if ! SMOKE_APP_IDENTITIES_BEFORE_LAUNCH="$(
|
|
356
|
+
process_records_for_exact_executable "$EXECUTABLE"
|
|
357
|
+
)"; then
|
|
358
|
+
echo "Recordings.app runtime smoke ${mode} could not inventory exact app processes before launch." >&2
|
|
359
|
+
return 1
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
"$ENV_EXECUTABLE" -i \
|
|
363
|
+
HOME="$HOME_DIRECTORY" \
|
|
364
|
+
PATH="$SANITIZED_PATH" \
|
|
365
|
+
TMPDIR="$WORK_DIR" \
|
|
366
|
+
"$OPEN_EXECUTABLE" -n -W "$APP_PATH" --args "${arguments[@]}" >"$log" 2>&1 &
|
|
367
|
+
SMOKE_PID=$!
|
|
368
|
+
SMOKE_PID_START_IDENTITY="$(
|
|
369
|
+
capture_process_start_identity "$SMOKE_PID" "$OPEN_EXECUTABLE"
|
|
370
|
+
)" || SMOKE_PID_START_IDENTITY=""
|
|
371
|
+
|
|
372
|
+
for ((attempt = 0; attempt < SMOKE_MAX_ATTEMPTS; attempt++)); do
|
|
373
|
+
if [ -z "$SMOKE_APP_PID" ]; then
|
|
374
|
+
bind_new_exact_app_process || true
|
|
375
|
+
fi
|
|
376
|
+
if [ -f "$output" ]; then
|
|
377
|
+
result_pid="$("$ENV_EXECUTABLE" -i HOME="$HOME_DIRECTORY" PATH="$SANITIZED_PATH" TMPDIR="$WORK_DIR" \
|
|
378
|
+
"$BUN_EXECUTABLE" -e 'const value = await Bun.file(process.argv[1]).json(); console.log(value.processIdentifier)' "$output")"
|
|
379
|
+
if ! [[ "$result_pid" =~ ^[1-9][0-9]*$ ]] || ! kill -0 "$result_pid" 2>/dev/null; then
|
|
380
|
+
echo "Recordings.app runtime smoke ${mode} reported a process that is not running." >&2
|
|
381
|
+
return 1
|
|
382
|
+
fi
|
|
383
|
+
if ! result_pid_start_identity="$(
|
|
384
|
+
capture_process_start_identity "$result_pid" "$EXECUTABLE"
|
|
385
|
+
)"; then
|
|
386
|
+
echo "Recordings.app runtime smoke ${mode} reported a process outside the exact app path." >&2
|
|
387
|
+
return 1
|
|
388
|
+
fi
|
|
389
|
+
if process_identity_existed_before_launch "$result_pid" "$result_pid_start_identity"; then
|
|
390
|
+
echo "Recordings.app runtime smoke ${mode} reported an app process that predated this launch." >&2
|
|
391
|
+
return 1
|
|
392
|
+
fi
|
|
393
|
+
if [ -n "$SMOKE_APP_PID" ] && {
|
|
394
|
+
[ "$result_pid" != "$SMOKE_APP_PID" ] ||
|
|
395
|
+
[ "$result_pid_start_identity" != "$SMOKE_APP_PID_START_IDENTITY" ];
|
|
396
|
+
}; then
|
|
397
|
+
echo "Recordings.app runtime smoke ${mode} evidence did not match the exact launched app process." >&2
|
|
398
|
+
return 1
|
|
399
|
+
fi
|
|
400
|
+
SMOKE_APP_PID="$result_pid"
|
|
401
|
+
SMOKE_APP_PID_START_IDENTITY="$result_pid_start_identity"
|
|
402
|
+
if [ -z "$SMOKE_PID_START_IDENTITY" ]; then
|
|
403
|
+
SMOKE_PID_START_IDENTITY="$(
|
|
404
|
+
capture_process_start_identity "$SMOKE_PID" "$OPEN_EXECUTABLE"
|
|
405
|
+
)" || SMOKE_PID_START_IDENTITY=""
|
|
406
|
+
fi
|
|
407
|
+
break
|
|
408
|
+
fi
|
|
409
|
+
if ! kill -0 "$SMOKE_PID" 2>/dev/null; then
|
|
410
|
+
echo "Recordings.app runtime smoke ${mode} exited without evidence." >&2
|
|
411
|
+
"$SED_EXECUTABLE" -n '1,120p' "$log" >&2
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
"$SLEEP_EXECUTABLE" 0.1
|
|
415
|
+
done
|
|
416
|
+
if [ ! -f "$output" ]; then
|
|
417
|
+
echo "Recordings.app runtime smoke ${mode} timed out." >&2
|
|
418
|
+
"$SED_EXECUTABLE" -n '1,120p' "$log" >&2
|
|
419
|
+
return 1
|
|
420
|
+
fi
|
|
421
|
+
if [ -z "$SMOKE_APP_PID" ]; then
|
|
422
|
+
echo "Recordings.app runtime smoke ${mode} did not bind evidence to the exact app process." >&2
|
|
423
|
+
return 1
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
"$ENV_EXECUTABLE" -i \
|
|
427
|
+
HOME="$HOME_DIRECTORY" \
|
|
428
|
+
PATH="$SANITIZED_PATH" \
|
|
429
|
+
TMPDIR="$WORK_DIR" \
|
|
430
|
+
SMOKE_MODE="$mode" \
|
|
431
|
+
SMOKE_FOCUS_EVIDENCE="$SMOKE_FOCUS_EVIDENCE" \
|
|
432
|
+
EXPECTED_HELPER="$APP_PATH/Contents/Helpers/recordings" \
|
|
433
|
+
"$BUN_EXECUTABLE" -e '
|
|
434
|
+
const path = process.argv[1];
|
|
435
|
+
const mode = process.env.SMOKE_MODE;
|
|
436
|
+
const result = await Bun.file(path).json();
|
|
437
|
+
const fail = (message) => { throw new Error(`${mode}: ${message}`); };
|
|
438
|
+
let focusEvidenceStatus = "available";
|
|
439
|
+
if (result.mode !== mode) fail("mode mismatch");
|
|
440
|
+
if (result.globalHandlersInstalled !== false) fail("global handlers were installed");
|
|
441
|
+
if (result.permissionRequestsStarted !== 0) fail("permission request path ran");
|
|
442
|
+
if (mode === "normal") {
|
|
443
|
+
if (result.menuBarSurfaceCount !== 1) fail("expected exactly one menu-bar surface");
|
|
444
|
+
if (result.accessibilityMenuBarItemCount !== 1) {
|
|
445
|
+
fail(`expected one accessible menu-bar item, got ${result.accessibilityMenuBarItemCount}`);
|
|
446
|
+
}
|
|
447
|
+
if (result.accessibilityObservationStatus !== "available") {
|
|
448
|
+
fail(`normal menu-bar AX observation was ${result.accessibilityObservationStatus}`);
|
|
449
|
+
}
|
|
450
|
+
if (!result.accessibilityMenuBarLabels.some((label) => label.includes("transcribing"))) {
|
|
451
|
+
fail(`accessible menu-bar label did not render transcribing state: ${JSON.stringify(result.accessibilityMenuBarLabels)}`);
|
|
452
|
+
}
|
|
453
|
+
const expected = ["Recordings", "Recordings, recording", "Recordings, transcribing"];
|
|
454
|
+
if (JSON.stringify(result.renderedStatusLabels) !== JSON.stringify(expected)) {
|
|
455
|
+
fail(`unexpected status labels ${JSON.stringify(result.renderedStatusLabels)}`);
|
|
456
|
+
}
|
|
457
|
+
if (result.windowCreationCount !== 1 || result.windowActivationCount !== 2) {
|
|
458
|
+
fail("retained-window activation path was not exercised twice");
|
|
459
|
+
}
|
|
460
|
+
if (result.retainedWindowReused !== true) fail("main window was not retained");
|
|
461
|
+
if (result.applicationActivationPolicy !== 0) fail("main window did not set regular activation policy");
|
|
462
|
+
if (!result.mainWindowIsVisible || !result.mainWindowCanBecomeKey) {
|
|
463
|
+
fail("retained main window was not visible and capable of becoming key");
|
|
464
|
+
}
|
|
465
|
+
if (!result.applicationIsActive || !result.mainWindowIsKey) {
|
|
466
|
+
if (process.env.SMOKE_FOCUS_EVIDENCE !== "ssh-unavailable") {
|
|
467
|
+
fail("Open Recordings did not make the retained window active and key");
|
|
468
|
+
}
|
|
469
|
+
focusEvidenceStatus = "ssh-unavailable";
|
|
470
|
+
}
|
|
471
|
+
} else if (mode === "resolver") {
|
|
472
|
+
if (result.menuBarSurfaceCount !== 0 || result.globalHandlersInstalled !== false) {
|
|
473
|
+
fail("resolver smoke installed UI surfaces or global handlers");
|
|
474
|
+
}
|
|
475
|
+
if (result.resolvedCompanionPath !== process.env.EXPECTED_HELPER) {
|
|
476
|
+
fail(`resolver selected ${JSON.stringify(result.resolvedCompanionPath)}`);
|
|
477
|
+
}
|
|
478
|
+
if (result.companionCapabilitiesPassed !== true) {
|
|
479
|
+
fail("packaged helper capability contract failed");
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
if (result.menuBarSurfaceCount !== 0) fail("permission/helper smoke inserted a menu-bar surface");
|
|
483
|
+
if (result.accessibilityObservationStatus !== "absent") {
|
|
484
|
+
fail(`permission/helper AX observation was ${result.accessibilityObservationStatus}`);
|
|
485
|
+
}
|
|
486
|
+
if (result.accessibilityMenuBarItemCount !== 0 || result.accessibilityMenuBarLabels.length !== 0) {
|
|
487
|
+
fail("permission/helper smoke exposed an accessible menu-bar surface");
|
|
488
|
+
}
|
|
489
|
+
if (result.renderedStatusLabels.length !== 0) fail("permission/helper smoke rendered a status label");
|
|
490
|
+
if (result.windowCreationCount !== 0 || result.windowActivationCount !== 0) {
|
|
491
|
+
fail("permission/helper smoke created or activated a window");
|
|
492
|
+
}
|
|
493
|
+
if (result.mainWindowIsVisible || result.mainWindowIsKey) {
|
|
494
|
+
fail("permission/helper smoke exposed a main window");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
console.log(JSON.stringify({
|
|
498
|
+
event: "recordings_runtime_smoke_evidence",
|
|
499
|
+
mode,
|
|
500
|
+
focusEvidenceStatus: mode === "normal" ? focusEvidenceStatus : "not-applicable",
|
|
501
|
+
applicationIsActive: result.applicationIsActive,
|
|
502
|
+
mainWindowIsKey: result.mainWindowIsKey,
|
|
503
|
+
}));
|
|
504
|
+
' "$output"
|
|
505
|
+
|
|
506
|
+
if [ -e "$completion" ]; then
|
|
507
|
+
echo "Recordings.app runtime smoke ${mode} produced completion before the challenge." >&2
|
|
508
|
+
return 1
|
|
509
|
+
fi
|
|
510
|
+
challenge="$("$ENV_EXECUTABLE" -i HOME="$HOME_DIRECTORY" PATH="$SANITIZED_PATH" TMPDIR="$WORK_DIR" \
|
|
511
|
+
"$BUN_EXECUTABLE" -e 'console.log(crypto.randomUUID())')"
|
|
512
|
+
if ! rechecked_start_identity="$(
|
|
513
|
+
capture_process_start_identity "$result_pid" "$EXECUTABLE"
|
|
514
|
+
)" || [ "$rechecked_start_identity" != "$result_pid_start_identity" ]; then
|
|
515
|
+
echo "Recordings.app runtime smoke ${mode} process identity changed before completion challenge." >&2
|
|
516
|
+
return 1
|
|
517
|
+
fi
|
|
518
|
+
write_atomic_text_file "$acknowledgement" "$challenge"
|
|
519
|
+
|
|
520
|
+
for ((completion_attempt = 0; completion_attempt < SMOKE_COMPLETION_ATTEMPTS; completion_attempt++)); do
|
|
521
|
+
kill -0 "$SMOKE_PID" 2>/dev/null || break
|
|
522
|
+
"$SLEEP_EXECUTABLE" 0.1
|
|
523
|
+
done
|
|
524
|
+
if kill -0 "$SMOKE_PID" 2>/dev/null; then
|
|
525
|
+
echo "Recordings.app runtime smoke ${mode} completion handshake timed out." >&2
|
|
526
|
+
return 1
|
|
527
|
+
fi
|
|
528
|
+
if wait "$SMOKE_PID" 2>/dev/null; then
|
|
529
|
+
wrapper_status=0
|
|
530
|
+
else
|
|
531
|
+
wrapper_status=$?
|
|
532
|
+
fi
|
|
533
|
+
SMOKE_PID=""
|
|
534
|
+
SMOKE_PID_START_IDENTITY=""
|
|
535
|
+
SMOKE_APP_PID=""
|
|
536
|
+
SMOKE_APP_PID_START_IDENTITY=""
|
|
537
|
+
if [ "$wrapper_status" -ne 0 ]; then
|
|
538
|
+
echo "Recordings.app runtime smoke ${mode} open -W wrapper exited unsuccessfully (${wrapper_status})." >&2
|
|
539
|
+
"$SED_EXECUTABLE" -n '1,120p' "$log" >&2
|
|
540
|
+
return 1
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
if ! "$ENV_EXECUTABLE" -i \
|
|
544
|
+
HOME="$HOME_DIRECTORY" \
|
|
545
|
+
PATH="$SANITIZED_PATH" \
|
|
546
|
+
TMPDIR="$WORK_DIR" \
|
|
547
|
+
SMOKE_CHALLENGE="$challenge" \
|
|
548
|
+
SMOKE_MODE="$mode" \
|
|
549
|
+
SMOKE_PROCESS_IDENTIFIER="$result_pid" \
|
|
550
|
+
"$BUN_EXECUTABLE" -e '
|
|
551
|
+
try {
|
|
552
|
+
const response = await Bun.file(process.argv[1]).json();
|
|
553
|
+
if (response.challenge !== process.env.SMOKE_CHALLENGE) throw new Error("challenge mismatch");
|
|
554
|
+
if (response.mode !== process.env.SMOKE_MODE) throw new Error("mode mismatch");
|
|
555
|
+
if (response.processIdentifier !== Number(process.env.SMOKE_PROCESS_IDENTIFIER)) {
|
|
556
|
+
throw new Error("process identifier mismatch");
|
|
557
|
+
}
|
|
558
|
+
} catch (error) {
|
|
559
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
' "$completion"; then
|
|
563
|
+
echo "Recordings.app runtime smoke ${mode} did not provide a valid completion response." >&2
|
|
564
|
+
return 1
|
|
565
|
+
fi
|
|
566
|
+
|
|
567
|
+
SMOKE_ACK_PATH=""
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
run_smoke normal
|
|
571
|
+
run_smoke permission-helper
|
|
572
|
+
run_smoke resolver
|
|
573
|
+
echo "Recordings.app runtime smoke passed: menu bar, retained window, helper isolation, and packaged resolver."
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import RecordingsLib
|
|
3
|
+
|
|
4
|
+
/// Root layout: a narrow violet Liquid-Glass sidebar on the left, and ONE continuous canvas
|
|
5
|
+
/// on the right — a compact header line, then either the Record workspace (default) or the
|
|
6
|
+
/// recordings library (list | detail), separated only by hairline dividers. No boxed panels.
|
|
7
|
+
struct ContentView: View {
|
|
8
|
+
@ObservedObject var store: RecordingsStore
|
|
9
|
+
@Environment(\.colorScheme) private var colorScheme
|
|
10
|
+
|
|
11
|
+
var body: some View {
|
|
12
|
+
HStack(spacing: 0) {
|
|
13
|
+
SidebarView(store: store)
|
|
14
|
+
.frame(width: Theme.sidebarWidth)
|
|
15
|
+
.background(Theme.sidebarGradient(colorScheme).ignoresSafeArea())
|
|
16
|
+
|
|
17
|
+
VStack(spacing: 0) {
|
|
18
|
+
HeaderBar(store: store)
|
|
19
|
+
Divider().opacity(0.5)
|
|
20
|
+
|
|
21
|
+
switch store.pane {
|
|
22
|
+
case .record:
|
|
23
|
+
RecordWorkspaceView(store: store)
|
|
24
|
+
case .library:
|
|
25
|
+
HSplitView {
|
|
26
|
+
RecordingsListView(store: store)
|
|
27
|
+
.frame(minWidth: 280, idealWidth: 340)
|
|
28
|
+
RecordingDetailView(store: store)
|
|
29
|
+
.frame(minWidth: 420)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
.background(Theme.canvas(colorScheme))
|
|
34
|
+
}
|
|
35
|
+
.frame(minWidth: 940, minHeight: 620)
|
|
36
|
+
.ignoresSafeArea(.container, edges: .top)
|
|
37
|
+
.onAppear { store.loadLibrary() }
|
|
38
|
+
.alert("Recordings Error", isPresented: Binding(
|
|
39
|
+
get: { store.operationError != nil },
|
|
40
|
+
set: { if !$0 { store.operationError = nil } }
|
|
41
|
+
)) {
|
|
42
|
+
Button("OK", role: .cancel) { store.operationError = nil }
|
|
43
|
+
} message: {
|
|
44
|
+
Text(store.operationError ?? "The operation failed.")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Compact header line: "12 recordings · Updated 3m ago" + actions.
|
|
50
|
+
private struct HeaderBar: View {
|
|
51
|
+
@ObservedObject var store: RecordingsStore
|
|
52
|
+
|
|
53
|
+
var body: some View {
|
|
54
|
+
HStack(spacing: 6) {
|
|
55
|
+
Text("\(store.library.count) recording\(store.library.count == 1 ? "" : "s")")
|
|
56
|
+
if let updated = store.lastUpdated {
|
|
57
|
+
Text("·")
|
|
58
|
+
Text("Updated \(updated.relativeDescription)")
|
|
59
|
+
}
|
|
60
|
+
Spacer()
|
|
61
|
+
|
|
62
|
+
Button { store.loadLibrary() } label: {
|
|
63
|
+
Image(systemName: "arrow.clockwise")
|
|
64
|
+
}
|
|
65
|
+
.buttonStyle(.plain)
|
|
66
|
+
.help("Refresh library")
|
|
67
|
+
|
|
68
|
+
Button {
|
|
69
|
+
store.pane = .record
|
|
70
|
+
} label: {
|
|
71
|
+
Image(systemName: "mic.circle.fill")
|
|
72
|
+
}
|
|
73
|
+
.buttonStyle(.plain)
|
|
74
|
+
.foregroundStyle(Theme.accent)
|
|
75
|
+
.help("New recording")
|
|
76
|
+
}
|
|
77
|
+
.font(.system(.subheadline, design: .rounded))
|
|
78
|
+
.foregroundStyle(.secondary)
|
|
79
|
+
.padding(.horizontal, 18)
|
|
80
|
+
.padding(.top, 20)
|
|
81
|
+
.padding(.bottom, 10)
|
|
82
|
+
}
|
|
83
|
+
}
|