@hasna/recordings 0.3.13 → 0.5.0
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 +279 -43
- package/bun.lock +37 -38
- package/contracts/v1/fixtures.json +1206 -0
- package/dist/cli/index.js +1909 -619
- package/dist/cli/macos-shortcut.d.ts +2 -2
- package/dist/contracts/hosted-v1.d.ts +105 -0
- package/dist/contracts/hosted-v1.d.ts.map +1 -0
- package/dist/contracts/hosted-v1.js +41 -0
- package/dist/contracts/stream-v1.d.ts +117 -0
- package/dist/contracts/stream-v1.d.ts.map +1 -0
- package/dist/contracts/stream-v1.js +35 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +6 -0
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/hosted/index.d.ts +58 -0
- package/dist/hosted/index.d.ts.map +1 -0
- package/dist/hosted/index.js +266 -0
- package/dist/hosted/transport.d.ts +36 -0
- package/dist/hosted/transport.d.ts.map +1 -0
- package/dist/hosted-v1-aavn7ktb.js +4114 -0
- package/dist/hosted-v1-gdr9extc.js +84 -0
- package/dist/http/client.d.ts +183 -22
- package/dist/http/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2879 -2288
- package/dist/lib/audio-artifact-storage.d.ts +98 -0
- package/dist/lib/audio-artifact-storage.d.ts.map +1 -0
- package/dist/lib/capture-probe.d.ts +3 -3
- package/dist/lib/capture-probe.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/local-opt-in.d.ts +132 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/macos-bundle.d.ts +1 -1
- package/dist/lib/persistence-probe.d.ts +56 -17
- package/dist/lib/persistence-probe.d.ts.map +1 -1
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1713 -459
- package/dist/sdk/index.d.ts +13 -5
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +904 -1
- package/dist/sdk/resolve.d.ts +53 -0
- package/dist/sdk/resolve.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +6 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/client-ip.d.ts +39 -0
- package/dist/server/client-ip.d.ts.map +1 -0
- package/dist/server/cloud-config.d.ts +12 -0
- package/dist/server/cloud-config.d.ts.map +1 -1
- package/dist/server/cloud-readiness.d.ts.map +1 -1
- package/dist/server/cloud.d.ts +20 -2
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +590 -287
- package/dist/server/migrate-command.d.ts +1 -1
- package/dist/server/migrate-command.d.ts.map +1 -1
- package/dist/server/openapi.d.ts +21 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1358 -874
- package/dist/store.d.ts +12 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -1
- package/docs/hosted-sdk.md +71 -0
- package/docs/wire-contracts.md +24 -0
- package/package.json +33 -11
- package/packaging/macos/build_release_pkg.sh +3 -3
- package/packaging/macos/managed_bootstrap.sh +4 -4
- package/packaging/macos/scripts/postinstall +2 -2
- package/packaging/macos/scripts/preinstall +2 -2
- package/scripts/build_companion_cli.sh +7 -1
- package/scripts/ci-linux-suite.ts +23 -13
- package/scripts/install_macos_app.sh +27 -24
- package/scripts/macos_artifact.ts +60 -49
- package/scripts/migrate.ts +4 -20
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +36 -4
- package/scripts/native-core-receipt.py +171 -0
- package/scripts/native_fs_guard.ts +2 -0
- package/scripts/release-suite-gate.ts +227 -150
- package/scripts/resolve_tailscale_cli.sh +24 -3
- package/scripts/smoke_macos_app.sh +21 -21
- package/src/native/Recordings/App/ContentView.swift +53 -68
- package/src/native/Recordings/App/MenuBarStatusView.swift +171 -61
- package/src/native/Recordings/App/RecordWorkspaceView.swift +47 -449
- package/src/native/Recordings/App/RecorderSettingsView.swift +75 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +3 -4
- package/src/native/Recordings/App/RecordingsApp.swift +185 -9
- package/src/native/Recordings/App/RecordingsListView.swift +60 -112
- package/src/native/Recordings/App/RecordingsStore.swift +124 -109
- package/src/native/Recordings/App/Theme.swift +73 -61
- package/src/native/Recordings/Package.swift +1 -0
- package/src/native/Recordings/RecordingsLib/BlockingOperation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/CaptureMonitor.swift +65 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +3 -3
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +75 -21
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +4 -4
- package/src/native/Recordings/RecordingsLib/Recording.swift +14 -2
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +539 -89
- package/src/native/Recordings/RecordingsLib/RecordingGlassBackground.swift +34 -0
- package/src/native/Recordings/RecordingsLib/RecordingPasteTarget.swift +114 -0
- package/src/native/Recordings/RecordingsLib/RecordingProvider.swift +112 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +17 -10
- package/src/native/Recordings/RecordingsLib/ServiceAPIConfiguration.swift +107 -0
- package/src/native/Recordings/RecordingsLib/ServiceConnectionView.swift +76 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -149
- package/src/native/Recordings/RecordingsLib/SettingsWindowController.swift +48 -0
- package/src/native/Recordings/RecordingsTests/BlockingOperationTests.swift +85 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +454 -76
- package/src/native/Recordings/RecordingsTests/OpenAIAPIKeyStoreTests.swift +43 -38
- package/src/native/Recordings/RecordingsTests/PipeClosureFixture.swift +215 -0
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +10 -10
- package/src/native/Recordings/RecordingsTests/RecorderSurfaceTests.swift +60 -0
- package/src/native/Recordings/RecordingsTests/RecordingDateTests.swift +26 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingFrozenPasteTargetTests.swift +50 -0
- package/src/native/Recordings/RecordingsTests/RecordingPasteTargetTrackerTests.swift +48 -0
- package/src/native/Recordings/RecordingsTests/RecordingProviderTests.swift +308 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +1 -1
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +123 -21
- package/src/native/Recordings/RecordingsTests/ServiceAPIConfigurationTests.swift +75 -0
- package/src/native/Recordings/RecordingsTests/SettingsWindowControllerTests.swift +28 -0
- package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +5 -1
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +2 -2
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +1 -1
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +1 -1
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +1 -1
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +2 -2
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +1 -1
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +2 -2
- package/src/native/Recordings/build.sh +8 -7
- package/dist/__tests__/helpers/installer-guard-execution.d.ts +0 -22
- package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +0 -1
- package/dist/__tests__/helpers/installer-preflight.d.ts +0 -22
- package/dist/__tests__/helpers/installer-preflight.d.ts.map +0 -1
- package/dist/__tests__/helpers/native-fs-guard.d.ts +0 -2
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +0 -1
- package/dist/__tests__/helpers/source-assertions.d.ts +0 -171
- package/dist/__tests__/helpers/source-assertions.d.ts.map +0 -1
- package/dist/__tests__/preload.d.ts +0 -2
- package/dist/__tests__/preload.d.ts.map +0 -1
- package/src/native/Recordings/App/SidebarView.swift +0 -212
|
@@ -51,7 +51,7 @@ enum RecordingsBootstrapPreflight {
|
|
|
51
51
|
let manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestData)
|
|
52
52
|
try validateManifest(manifest, payload: payload)
|
|
53
53
|
|
|
54
|
-
let app = arguments.payloadRoot + "/Applications/
|
|
54
|
+
let app = arguments.payloadRoot + "/Applications/Hasna Recordings.app"
|
|
55
55
|
let client = app + "/" + RecordingsUpdateConstants.updateClientRelativePath
|
|
56
56
|
let broker = arguments.payloadRoot + "/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
57
57
|
let artifactVerifier = arguments.payloadRoot
|
|
@@ -7,7 +7,7 @@ import RecordingsUpdateProtocol
|
|
|
7
7
|
/// operation; first installs use exclusive rename semantics and can never replace a
|
|
8
8
|
/// leaf created concurrently in `/Applications`.
|
|
9
9
|
final class ApplicationNamespace {
|
|
10
|
-
private static let liveLeaf = "
|
|
10
|
+
private static let liveLeaf = "Hasna Recordings.app"
|
|
11
11
|
private static let previousLeaf = "previous.app"
|
|
12
12
|
private static let failedCandidateLeaf = "failed-candidate.app"
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ final class ApplicationNamespace {
|
|
|
22
22
|
.deletingLastPathComponent
|
|
23
23
|
guard journal.applicationPath == RecordingsUpdateConstants.applicationPath,
|
|
24
24
|
journal.candidateApplicationPath == expectedTransactionDirectory
|
|
25
|
-
+ "/candidate/
|
|
25
|
+
+ "/candidate/Hasna Recordings.app",
|
|
26
26
|
journal.previousApplicationPath == nil ||
|
|
27
27
|
journal.previousApplicationPath == expectedTransactionDirectory + "/previous.app"
|
|
28
28
|
else {
|
|
@@ -276,7 +276,7 @@ final class UpdateBrokerSession: NSObject, RecordingsUpdateXPCProtocol {
|
|
|
276
276
|
guard mkdir(candidateRoot, 0o700) == 0 else {
|
|
277
277
|
throw BrokerOperationError.candidateRejected
|
|
278
278
|
}
|
|
279
|
-
let candidateApplication = candidateRoot + "/
|
|
279
|
+
let candidateApplication = candidateRoot + "/Hasna Recordings.app"
|
|
280
280
|
let verifierIDs = try verifierAccountIDs()
|
|
281
281
|
try CanonicalTreeCopier.copyApplication(
|
|
282
282
|
from: verifierOutput,
|
|
@@ -22,7 +22,7 @@ enum CanonicalTreeCopier {
|
|
|
22
22
|
}
|
|
23
23
|
defer { Darwin.close(source) }
|
|
24
24
|
let destinationRoot = URL(fileURLWithPath: rootCandidatePath).deletingLastPathComponent().path
|
|
25
|
-
guard URL(fileURLWithPath: rootCandidatePath).lastPathComponent == "
|
|
25
|
+
guard URL(fileURLWithPath: rootCandidatePath).lastPathComponent == "Hasna Recordings.app" else {
|
|
26
26
|
throw CanonicalCopyError.nonCanonicalRoot
|
|
27
27
|
}
|
|
28
28
|
let destination = Darwin.open(
|
|
@@ -253,7 +253,7 @@ struct DurableInstallJournal {
|
|
|
253
253
|
let leaf = URL(fileURLWithPath: transactionDirectory).lastPathComponent
|
|
254
254
|
guard leaf.hasPrefix("transaction-") else { throw InstallJournalError.invalidJournal }
|
|
255
255
|
let transactionID = String(leaf.dropFirst("transaction-".count))
|
|
256
|
-
let expectedCandidate = transactionDirectory + "/candidate/
|
|
256
|
+
let expectedCandidate = transactionDirectory + "/candidate/Hasna Recordings.app"
|
|
257
257
|
let activationPhases: Set<String> = [
|
|
258
258
|
"prepared", "launch-barrier-pending", "launch-barrier-held",
|
|
259
259
|
"swap-pending", "swapped", "previous-retaining",
|
|
@@ -201,8 +201,8 @@ struct ActivationRecoveryPolicyTests {
|
|
|
201
201
|
"Contents/Helpers/recordings": 493,
|
|
202
202
|
"Contents/Helpers/recordings-update-client": 493
|
|
203
203
|
},
|
|
204
|
-
"candidate_application_path": "/Library/Application Support/Hasna/Recordings/Updates/transaction-00000000-0000-0000-0000-000000000001/candidate/
|
|
205
|
-
"application_path": "/Applications/
|
|
204
|
+
"candidate_application_path": "/Library/Application Support/Hasna/Recordings/Updates/transaction-00000000-0000-0000-0000-000000000001/candidate/Hasna Recordings.app",
|
|
205
|
+
"application_path": "/Applications/Hasna Recordings.app"
|
|
206
206
|
}
|
|
207
207
|
"""#.utf8)
|
|
208
208
|
let journal = try JSONDecoder().decode(BrokerInstallJournal.self, from: legacyJSON)
|
|
@@ -7,7 +7,7 @@ public enum RecordingsUpdateConstants {
|
|
|
7
7
|
public static let rootMaintenanceSupported = false
|
|
8
8
|
public static let keyRotationSupported = false
|
|
9
9
|
public static let machServiceName = "com.hasna.recordings.updater"
|
|
10
|
-
public static let applicationPath = "/Applications/
|
|
10
|
+
public static let applicationPath = "/Applications/Hasna Recordings.app"
|
|
11
11
|
public static let brokerExecutablePath = "/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
12
12
|
public static let stateRoot = "/Library/Application Support/Hasna/Recordings/Updates"
|
|
13
13
|
public static let trustRoot = "/Library/Application Support/Hasna/Recordings/Trust"
|
|
@@ -729,7 +729,7 @@ int recordings_copy_canonical_application_tree(
|
|
|
729
729
|
if (entry == NULL) break;
|
|
730
730
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
731
731
|
visible_entries += 1;
|
|
732
|
-
if (strcmp(entry->d_name, "
|
|
732
|
+
if (strcmp(entry->d_name, "Hasna Recordings.app") != 0) only_application = false;
|
|
733
733
|
}
|
|
734
734
|
int status = errno;
|
|
735
735
|
closedir(directory);
|
|
@@ -739,7 +739,7 @@ int recordings_copy_canonical_application_tree(
|
|
|
739
739
|
status = copy_directory_at(
|
|
740
740
|
verifier_output_directory_descriptor,
|
|
741
741
|
root_candidate_directory_descriptor,
|
|
742
|
-
"
|
|
742
|
+
"Hasna Recordings.app",
|
|
743
743
|
verifier_user_id,
|
|
744
744
|
&budget
|
|
745
745
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Build, sign, notarize, and finalize a
|
|
2
|
+
# Build, sign, notarize, and finalize a Hasna Recordings.app artifact.
|
|
3
3
|
# Usage: ./build.sh [debug|local] | ./build.sh release <initial-bootstrap|app-update>
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
@@ -120,11 +120,11 @@ if [ "$VARIANT" = "bar" ]; then
|
|
|
120
120
|
fi
|
|
121
121
|
# Fleet naming rule (knowledge k_msxd5rz3_jfvl3i): every Hasna macOS app is
|
|
122
122
|
# Hasna<Name>.app with 'Hasna' at the beginning. The bundle is named
|
|
123
|
-
#
|
|
123
|
+
# Hasna Recordings.app for BOTH variants (full and bar); the 'bar' is a variant, never
|
|
124
124
|
# a separate app name. Bundle identifier stays com.hasna.recordings so TCC keeps
|
|
125
125
|
# keying on bundle id + signing identity, and the executable inside the bundle stays
|
|
126
126
|
# "Recordings".
|
|
127
|
-
APP_BUNDLE_NAME="
|
|
127
|
+
APP_BUNDLE_NAME="Hasna Recordings.app"
|
|
128
128
|
readonly APP_BUNDLE_NAME
|
|
129
129
|
APP_BASENAME="${APP_BUNDLE_NAME%.app}"
|
|
130
130
|
PLIST_BUDDY="$(select_executable "/usr/libexec/PlistBuddy" "${RECORDINGS_TEST_PLIST_BUDDY_EXECUTABLE:-${PLIST_BUDDY:-}}")"
|
|
@@ -505,7 +505,8 @@ generate_and_verify_native_fs_guard() {
|
|
|
505
505
|
import { createRequire } from "node:module";
|
|
506
506
|
const addon = createRequire(import.meta.url)(process.argv[1]);
|
|
507
507
|
const expected = [
|
|
508
|
-
"chmodHandle", "close", "copyRegularNoReplaceAt", "
|
|
508
|
+
"chmodHandle", "close", "copyRegularNoReplaceAt", "duplicateDirectoryDescriptor",
|
|
509
|
+
"fsyncHandle", "handleHasNoExtendedAcl",
|
|
509
510
|
"linkNoReplaceAt", "mkdirAt", "openDirAt", "openRegularAt", "openTrustedHome",
|
|
510
511
|
"readDir", "readRegularAt", "removeTreeAt", "removeTreeHandleAt",
|
|
511
512
|
"renameHandleNoReplaceAt", "renameNoReplaceAt", "renameReplaceAt",
|
|
@@ -1256,7 +1257,7 @@ fi
|
|
|
1256
1257
|
|
|
1257
1258
|
generate_and_verify_native_fs_guard
|
|
1258
1259
|
|
|
1259
|
-
echo "Building
|
|
1260
|
+
echo "Building Hasna Recordings.app ($MODE)..."
|
|
1260
1261
|
if [ "$MODE" = "release" ]; then
|
|
1261
1262
|
OUTPUT_BUILD_DIR="$BUILD_ROOT/release-output"
|
|
1262
1263
|
else
|
|
@@ -1992,7 +1993,7 @@ if [ "$MODE" = "local" ]; then
|
|
|
1992
1993
|
fi
|
|
1993
1994
|
|
|
1994
1995
|
verify_signed_code "$HELPERS/recordings" "Companion CLI"
|
|
1995
|
-
verify_signed_code "$APP_DIR" "
|
|
1996
|
+
verify_signed_code "$APP_DIR" "Hasna Recordings.app"
|
|
1996
1997
|
ARTIFACT_BASENAME="${APP_BASENAME}-${VERSION}-macos-${RELEASE_SUBTYPE}"
|
|
1997
1998
|
NOTARY_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}-notarization.zip"
|
|
1998
1999
|
FINAL_ARCHIVE="$OUTPUT_BUILD_DIR/${ARTIFACT_BASENAME}.zip"
|
|
@@ -2044,7 +2045,7 @@ run_xcrun stapler validate "$APP_DIR"
|
|
|
2044
2045
|
run_release_sensitive_tool "$SPCTL_EXECUTABLE" --assess --type execute --verbose=2 "$APP_DIR"
|
|
2045
2046
|
run_release_sensitive_tool "$SYSPOLICY_CHECK_EXECUTABLE" distribution "$APP_DIR"
|
|
2046
2047
|
verify_signed_code "$HELPERS/recordings" "Companion CLI"
|
|
2047
|
-
verify_signed_code "$APP_DIR" "
|
|
2048
|
+
verify_signed_code "$APP_DIR" "Hasna Recordings.app"
|
|
2048
2049
|
run_codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
|
2049
2050
|
|
|
2050
2051
|
run_release_sensitive_tool "$DITTO_EXECUTABLE" -c -k --sequesterRsrc --keepParent "$APP_DIR" "$FINAL_ARCHIVE"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export declare const EXISTING_IDENTITY_SHA256: string;
|
|
2
|
-
export declare const CANDIDATE_IDENTITY_SHA256: string;
|
|
3
|
-
export declare const MANIFEST_SHA256: string;
|
|
4
|
-
export type ArtifactPolicy = "local-only" | "release";
|
|
5
|
-
export type IncompatibleDirection = "installed-requirement-vs-candidate" | "candidate-requirement-vs-installed" | "both";
|
|
6
|
-
export type GuardExecutionOptions = {
|
|
7
|
-
identityMigration: boolean;
|
|
8
|
-
incompatibleDirection?: IncompatibleDirection;
|
|
9
|
-
artifactPolicy?: ArtifactPolicy;
|
|
10
|
-
extraArguments?: string[];
|
|
11
|
-
};
|
|
12
|
-
export type GuardExecutionResult = {
|
|
13
|
-
exitCode: number;
|
|
14
|
-
stdout: string;
|
|
15
|
-
stderr: string;
|
|
16
|
-
reachedTransaction: boolean;
|
|
17
|
-
codesignInvocations: string[];
|
|
18
|
-
bunInvocations: string[];
|
|
19
|
-
unstubbedInvocations: string[];
|
|
20
|
-
};
|
|
21
|
-
export declare function runInstallerToIdentityGuard(options: GuardExecutionOptions): GuardExecutionResult;
|
|
22
|
-
//# sourceMappingURL=installer-guard-execution.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"installer-guard-execution.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-guard-execution.ts"],"names":[],"mappings":"AA+DA,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AACvD,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AACxD,eAAO,MAAM,eAAe,QAAiB,CAAC;AAsB9C,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,CAAC;AAuFtD,MAAM,MAAM,qBAAqB,GAE7B,oCAAoC,GAGpC,oCAAoC,GAEpC,MAAM,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAKlC,iBAAiB,EAAE,OAAO,CAAC;IAG3B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAK9C,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAKf,kBAAkB,EAAE,OAAO,CAAC;IAG5B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAG9B,cAAc,EAAE,MAAM,EAAE,CAAC;IAMzB,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CA2StB"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export declare const POLICY_RELATIVE_PATH = "scripts/policy/local-only-approved-targets.txt";
|
|
2
|
-
export declare const READER_RELATIVE_PATH = "scripts/read_local_only_targets.sh";
|
|
3
|
-
export declare const IDENTITY_GUARD_RELATIVE_PATH = "scripts/enforce_identity_migration.sh";
|
|
4
|
-
export declare const readRepositoryFile: (relativePath: string) => string;
|
|
5
|
-
export type InstallerPreflightOptions = {
|
|
6
|
-
artifactPolicy?: "release" | "local-only";
|
|
7
|
-
approvedTarget?: string;
|
|
8
|
-
hostname?: string;
|
|
9
|
-
extraArguments?: string[];
|
|
10
|
-
policyContents?: string | null;
|
|
11
|
-
removeReader?: boolean;
|
|
12
|
-
removeIdentityGuard?: boolean;
|
|
13
|
-
identityGuardContents?: string;
|
|
14
|
-
symlinkIdentityGuard?: boolean;
|
|
15
|
-
environment?: Record<string, string>;
|
|
16
|
-
};
|
|
17
|
-
export declare function runInstallerPreflight(options?: InstallerPreflightOptions): {
|
|
18
|
-
exitCode: number;
|
|
19
|
-
stdout: string;
|
|
20
|
-
stderr: string;
|
|
21
|
-
};
|
|
22
|
-
//# sourceMappingURL=installer-preflight.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"installer-preflight.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-preflight.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,mDAAmD,CAAC;AACrF,eAAO,MAAM,oBAAoB,uCAAuC,CAAC;AACzE,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AAEpF,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MACA,CAAC;AAc3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,cAAc,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,yBAA8B,GACtC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA8GtD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"native-fs-guard.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/native-fs-guard.ts"],"names":[],"mappings":"AA2CA,wBAAgB,wBAAwB,CAAC,cAAc,SAAuC,GAAG,MAAM,CActG"}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared assertions for contract tests that read source text.
|
|
3
|
-
*
|
|
4
|
-
* These live here rather than inside one suite because the defect they exist to prevent is
|
|
5
|
-
* repo-wide: a sweep of every `*.test.ts` found 40 ordering assertions written as
|
|
6
|
-
* `indexOf(a) < indexOf(b)`, across 11 files. `indexOf` answers -1 when the needle is absent and
|
|
7
|
-
* `-1 < anything` is true, so such an assertion PASSES when the thing being ordered is DELETED.
|
|
8
|
-
* The same hole exists in `.slice(indexOf(...), indexOf(...))` region bounds, where a -1 silently
|
|
9
|
-
* slices from the end of the file or to its start — a region assertion over the wrong text, or over
|
|
10
|
-
* none of it, reads exactly like a satisfied one.
|
|
11
|
-
*
|
|
12
|
-
* ---------------------------------------------------------------------------------------------
|
|
13
|
-
* BEFORE YOU BUILD A MUTATION BATTERY: three suites are RED ON A CONTENDED STATION, not on Linux.
|
|
14
|
-
*
|
|
15
|
-
* A mutation battery is evidence only when its clean control is GREEN. Include a suite that was
|
|
16
|
-
* already failing and the run was non-zero before you changed anything, so every mutation "looks
|
|
17
|
-
* caught" and every verdict is manufactured. That already produced one wrong all-clear here.
|
|
18
|
-
*
|
|
19
|
-
* These are the three suites it happens to. NO PASS/FAIL SPLIT IS RECORDED FOR THEM ON PURPOSE —
|
|
20
|
-
* see below. `macos-app-lifecycle.test.ts` has 140 tests, `native-app-companion-contract.test.ts`
|
|
21
|
-
* has 14, `config.test.ts` has 44, and how many of those fail is a property of the MACHINE:
|
|
22
|
-
*
|
|
23
|
-
* src/__tests__/macos-app-lifecycle.test.ts
|
|
24
|
-
* src/__tests__/native-app-companion-contract.test.ts
|
|
25
|
-
* src/__tests__/config.test.ts
|
|
26
|
-
*
|
|
27
|
-
* CORRECTED 2026-07-27, and the correction is the useful part. This block used to call them
|
|
28
|
-
* "PERMANENTLY RED on Linux" and attribute fixed environmental causes — BSD `stat -f`, a fixture
|
|
29
|
-
* port reading `NaN`, a `getDataDir` HOME-ancestor assumption. The first CI run this repository
|
|
30
|
-
* ever had (run 30302342895, ubuntu-24.04) re-ran all three on a clean single-tenant runner and
|
|
31
|
-
* every one of them PASSED:
|
|
32
|
-
*
|
|
33
|
-
* macos-app-lifecycle 140 pass / 0 fail (358.85s)
|
|
34
|
-
* native-app-companion-contract 14 pass / 0 fail ( 4.50s)
|
|
35
|
-
* config 44 pass / 0 fail ( 0.10s)
|
|
36
|
-
*
|
|
37
|
-
* So the cause is not the platform. Measure on a quiet machine, or in CI, before recording a suite
|
|
38
|
-
* as red. There were TWO independent station-local causes, and every earlier version of this comment
|
|
39
|
-
* named only one of them. In a 92-fail run of `macos-app-lifecycle.test.ts` on this station the
|
|
40
|
-
* failure messages broke down as 38 × `Home ancestor has an unexpected owner.`, 22 × FIFO
|
|
41
|
-
* synchronisation timeout, 24 × ENOENT on a fixture marker. The first cause is now fixed in the
|
|
42
|
-
* fixture and only the second remains; do not collapse them into one, because a quiet machine with
|
|
43
|
-
* `FORCE_COLOR` set still showed all 38 aborts, so "measure on a quiet machine" was necessary and
|
|
44
|
-
* not sufficient:
|
|
45
|
-
*
|
|
46
|
-
* 1. `FORCE_COLOR` — **FIXED IN THE FIXTURE, no longer a live cause.** The `stat` stub answered
|
|
47
|
-
* `%u` by shelling out to `bun -e '… console.log(statSync(…).uid)'`
|
|
48
|
-
* (`macos-app-lifecycle.test.ts:217`) and spread `...Bun.env` into the installer, so with
|
|
49
|
-
* `FORCE_COLOR` set Bun COLOURED the number: the installer compared `\e[0m\e[33m1000\e[0m`
|
|
50
|
-
* against `id -u`'s `1000` at `install_macos_app.sh:143` and aborted before reaching any gate.
|
|
51
|
-
* `NO_COLOR=1` did NOT help — FORCE_COLOR wins in Bun. The stub now writes bare integers with
|
|
52
|
-
* `process.stdout.write` and `unset FORCE_COLOR`s its own children, which takes that message
|
|
53
|
-
* from 38 to 0 in place, positive-controlled (the same grep still finds 38 in the pre-fix log).
|
|
54
|
-
* Nothing about ancestor MODE was ever involved: `verify_secure_parent` and
|
|
55
|
-
* `verify_safe_home_ancestor` each `stat` only the one path handed to them, the sole call is
|
|
56
|
-
* `verify_safe_home_ancestor "$HOME"`, and the stub hardcodes every `%Lp` answer anyway.
|
|
57
|
-
* Kept here rather than deleted because it is the reason this file's split moved, and because
|
|
58
|
-
* the same trap recurs in any stub that parses `console.log` of a NUMBER: only strings are
|
|
59
|
-
* left uncoloured.
|
|
60
|
-
* 2. CONTENTION. The station routinely runs several full recordings suites at once out of
|
|
61
|
-
* different worktrees, and this suite scans a shared /tmp — the hazard this very comment warns
|
|
62
|
-
* about below. Those were FIFO timeouts at an internal 5000ms budget; the FIFO helper now uses
|
|
63
|
-
* the suite's configured timeout too, so a raised test budget can distinguish load sensitivity
|
|
64
|
-
* from a regression. With `FORCE_COLOR` unset the residual failures scaled with load: 132 pass /
|
|
65
|
-
* 8 fail at load ~20, 114 pass / 26 fail at load 44-60 on the same commit, which is why no split
|
|
66
|
-
* belongs here either. GitHub Actions sets neither `FORCE_COLOR` nor a competing suite, which
|
|
67
|
-
* is why both causes were absent from the only clean measurement.
|
|
68
|
-
*
|
|
69
|
-
* WHY NO SPLIT IS RECORDED. Every split ever written here has gone stale, including two written as
|
|
70
|
-
* corrections. On one unchanged tree, three consecutive runs measured 48/92, 48/92, 49/91; the
|
|
71
|
-
* single test that flips is `runtime smoke timeout does not wait forever on a live open process`
|
|
72
|
-
* (`macos-app-lifecycle.test.ts:3594`), which races a hardcoded internal `Bun.sleep(2_000)` that no
|
|
73
|
-
* `--timeout` flag reaches either. So a count comparison across two trees shows a phantom delta
|
|
74
|
-
* from this file alone — which is the concrete reason for the rule below: compare failing test
|
|
75
|
-
* NAMES, never counts. Two trees whose failing NAME SETS are identical are identical regardless of
|
|
76
|
-
* what the totals say.
|
|
77
|
-
*
|
|
78
|
-
* `@hasna/events` MUST resolve 0.1.11, as `bun.lock` pins it. A plain `bun install` pulls 0.1.14,
|
|
79
|
-
* which dropped a shipped CLI command inside the patch range and fails `cli.test.ts` — and it can
|
|
80
|
-
* drift back mid-session, so re-check it before quoting any cross-tree comparison.
|
|
81
|
-
*
|
|
82
|
-
* Corollary, also corrected: this repo NOW HAS CI. The repo-root
|
|
83
|
-
* `.github/workflows/ci.yml` gates the whole TypeScript suite with no exemptions on every
|
|
84
|
-
* pull request, so these suites are no longer gated only by somebody remembering to run
|
|
85
|
-
* them. The Swift/C half compiles in CI through the repo-root
|
|
86
|
-
* `.github/workflows/recordings-macos.yml` (`verify:ci-native`); see
|
|
87
|
-
* `.github/native-known-errors.txt` for what that gate means.
|
|
88
|
-
* Compare failing test NAMES, never counts — the suite is nondeterministic at the margin.
|
|
89
|
-
* ---------------------------------------------------------------------------------------------
|
|
90
|
-
*/
|
|
91
|
-
/**
|
|
92
|
-
* Assert `first` appears before `second`, requiring BOTH to exist.
|
|
93
|
-
*
|
|
94
|
-
* Use this instead of comparing two `indexOf` results directly. `firstMatch: "last"` selects
|
|
95
|
-
* `lastIndexOf` for the first operand, which has the identical -1 hole.
|
|
96
|
-
*/
|
|
97
|
-
export declare function expectOrder(haystack: string, first: string, second: string, options?: {
|
|
98
|
-
firstMatch?: "first" | "last";
|
|
99
|
-
}): void;
|
|
100
|
-
/**
|
|
101
|
-
* Slice between two markers, requiring both to exist and the region to be non-trivial.
|
|
102
|
-
*
|
|
103
|
-
* The length floor matters as much as the -1 checks: `slice(-1)` yields a ONE-CHARACTER string, not
|
|
104
|
-
* an empty one, so a `expect(region.length).toBeGreaterThan(0)` control passes on a region that
|
|
105
|
-
* contains nothing worth asserting about. Any `not.toContain` over such a region is vacuous.
|
|
106
|
-
*/
|
|
107
|
-
export declare function sliceBetween(source: string, open: string, close: string, options?: {
|
|
108
|
-
minimumLength?: number;
|
|
109
|
-
}): string;
|
|
110
|
-
/**
|
|
111
|
-
* Assert a marker occurs exactly once before slicing on it.
|
|
112
|
-
*
|
|
113
|
-
* A duplicated end marker silently extends a region: `let myPID = ProcessInfo…` occurs twice in
|
|
114
|
-
* `RecordingEngine.swift`, so a region bounded by it could stretch ~127 KB and be satisfied by
|
|
115
|
-
* copies of the needle from an unrelated function.
|
|
116
|
-
*/
|
|
117
|
-
export declare function sliceBetweenUnique(source: string, open: string, close: string): string;
|
|
118
|
-
/** Strip Swift line comments so an assertion about code is not defeated by prose. */
|
|
119
|
-
export declare function withoutComments(source: string): string;
|
|
120
|
-
/**
|
|
121
|
-
* Index just past the `close` matching the `open` at `openIndex`, skipping comments and literals.
|
|
122
|
-
*
|
|
123
|
-
* Needed because `lastIndexOf("}")` is not brace matching, and the difference is a live defect: an
|
|
124
|
-
* early exit written between a decision and its use —
|
|
125
|
-
*
|
|
126
|
-
* } // the decision table closes here
|
|
127
|
-
* guard stillOwnsChangeCount else { return }
|
|
128
|
-
* if shouldRestore { … }
|
|
129
|
-
*
|
|
130
|
-
* — puts a NEARER `}` (the `else` block's) between the two, so a "nothing between them" check
|
|
131
|
-
* measured from the last brace saw only whitespace and passed. Counting braces naively fails the
|
|
132
|
-
* other way: one `}` inside a string literal, such as `log("settlement }")`, cancels a real opener.
|
|
133
|
-
* Both were measured surviving at EXIT=0.
|
|
134
|
-
*/
|
|
135
|
-
export declare function matchingDelimiterIndex(source: string, openIndex: number, open: string, close: string): number;
|
|
136
|
-
export declare function withoutAnyComments(source: string): string;
|
|
137
|
-
/**
|
|
138
|
-
* The arms of a Swift `switch` body, as a mapping from each matched case to its expression.
|
|
139
|
-
*
|
|
140
|
-
* Order- and grouping-independent, which matters because pinning an arm by its exact TEXT gets both
|
|
141
|
-
* directions wrong. It false-positives on a pure reorder — `.deliveredUnverified, .deliveryNotObserved`
|
|
142
|
-
* is the same table and failed — and it misses a real defect: splitting one outcome out of a group
|
|
143
|
-
* into its own arm with a different expression leaves the pinned needle intact, so
|
|
144
|
-
* `.targetUnavailable` could be given `false` while the six-outcome needle still matched.
|
|
145
|
-
*
|
|
146
|
-
* `body` is the text between the switch's braces, comments already stripped by the caller.
|
|
147
|
-
*/
|
|
148
|
-
export declare function switchArmsByOutcome(body: string): Map<string, string>;
|
|
149
|
-
/**
|
|
150
|
-
* Evaluate a Swift boolean condition for a given binding of its identifiers.
|
|
151
|
-
*
|
|
152
|
-
* Folded in from PR #43, which pinned the clipboard-restore guard by evaluating it for BOTH values
|
|
153
|
-
* of the decision rather than comparing its text. That is the half worth keeping: an exact
|
|
154
|
-
* `toBe("shouldRestore")` on a captured condition kills the inversion, but it also fails on
|
|
155
|
-
* `(shouldRestore)` and on a trailing comment — reporting refactors as defects while proving
|
|
156
|
-
* nothing about behaviour.
|
|
157
|
-
*
|
|
158
|
-
* Deliberately narrow and fail-CLOSED: `true`, `false`, `!x`, whole-expression parentheses, and
|
|
159
|
-
* identifiers present in `env`. Anything else throws. An added disjunct
|
|
160
|
-
* (`shouldRestore || stillOwnsChangeCount`) is an unevaluatable expression, not a passing one —
|
|
161
|
-
* which is the behaviour that matters, because that disjunct is exactly how the
|
|
162
|
-
* transcript-destroying defect gets reintroduced. Extend the evaluator when a new shape is
|
|
163
|
-
* legitimate; do not loosen the assertion.
|
|
164
|
-
*/
|
|
165
|
-
export declare function evaluateSwiftCondition(condition: string, env: Record<string, boolean>): boolean;
|
|
166
|
-
/**
|
|
167
|
-
* Every Swift source under a root, so an absence claim can be made about the app rather than about
|
|
168
|
-
* whichever files were on the reviewer's mind.
|
|
169
|
-
*/
|
|
170
|
-
export declare function swiftSourcesUnder(root: string): Array<[path: string, source: string]>;
|
|
171
|
-
//# sourceMappingURL=source-assertions.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"source-assertions.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/source-assertions.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAAO,GAC9C,IAAI,CAON;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,MAAM,CAWR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAuED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,MAAM,CA0BR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAiC/F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAIrF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../../src/__tests__/preload.ts"],"names":[],"mappings":""}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import SwiftUI
|
|
2
|
-
import RecordingsLib
|
|
3
|
-
|
|
4
|
-
/// Narrow violet sidebar: Record · Library · Projects · Modes · Machines. White text/tint
|
|
5
|
-
/// over the gradient; selected rows get a translucent white highlight (Liquid Glass refracts
|
|
6
|
-
/// the violet beneath).
|
|
7
|
-
struct SidebarView: View {
|
|
8
|
-
@ObservedObject var store: RecordingsStore
|
|
9
|
-
@Environment(\.colorScheme) private var colorScheme
|
|
10
|
-
|
|
11
|
-
@State private var addingProject = false
|
|
12
|
-
@State private var newProjectName = ""
|
|
13
|
-
|
|
14
|
-
var body: some View {
|
|
15
|
-
ScrollView {
|
|
16
|
-
VStack(alignment: .leading, spacing: 18) {
|
|
17
|
-
workspaceSection
|
|
18
|
-
librarySection
|
|
19
|
-
projectsSection
|
|
20
|
-
if hasAnyMode { modesSection }
|
|
21
|
-
if !store.machines.isEmpty { machinesSection }
|
|
22
|
-
Spacer(minLength: 8)
|
|
23
|
-
}
|
|
24
|
-
.padding(14)
|
|
25
|
-
.padding(.top, 24)
|
|
26
|
-
}
|
|
27
|
-
.scrollContentBackground(.hidden)
|
|
28
|
-
.foregroundStyle(.white)
|
|
29
|
-
.tint(.white)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// MARK: - Workspace
|
|
33
|
-
|
|
34
|
-
private var workspaceSection: some View {
|
|
35
|
-
section(title: "Workspace") {
|
|
36
|
-
Button {
|
|
37
|
-
withAnimation(.spring(response: 0.3, dampingFraction: 0.85)) { store.pane = .record }
|
|
38
|
-
} label: {
|
|
39
|
-
rowLabel(icon: "mic.fill", label: "Record", count: nil,
|
|
40
|
-
selected: store.pane == .record, accentDot: store.engine.captureIsActive)
|
|
41
|
-
}
|
|
42
|
-
.buttonStyle(.plain)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// MARK: - Library
|
|
47
|
-
|
|
48
|
-
private var librarySection: some View {
|
|
49
|
-
section(title: "Library") {
|
|
50
|
-
filterRow(.all, icon: "waveform", label: "All Recordings")
|
|
51
|
-
if store.count(for: .noProject) > 0 {
|
|
52
|
-
filterRow(.noProject, icon: "tray", label: "No Project")
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// MARK: - Projects
|
|
58
|
-
|
|
59
|
-
private var projectsSection: some View {
|
|
60
|
-
section(title: "Projects", trailing: {
|
|
61
|
-
Button { addingProject = true; newProjectName = "" } label: {
|
|
62
|
-
Image(systemName: "plus").font(.system(size: 11, weight: .semibold))
|
|
63
|
-
}
|
|
64
|
-
.buttonStyle(.plain)
|
|
65
|
-
.disabled(!store.projectStore.canMutateProjects)
|
|
66
|
-
.help("Add Project")
|
|
67
|
-
.popover(isPresented: $addingProject, arrowEdge: .trailing) {
|
|
68
|
-
VStack(alignment: .leading, spacing: 10) {
|
|
69
|
-
Text("New Project").font(.headline)
|
|
70
|
-
TextField("Project name", text: $newProjectName)
|
|
71
|
-
.textFieldStyle(.roundedBorder).frame(width: 200)
|
|
72
|
-
.onSubmit(commitNewProject)
|
|
73
|
-
HStack {
|
|
74
|
-
Spacer()
|
|
75
|
-
Button("Cancel") { addingProject = false }
|
|
76
|
-
Button("Add") { commitNewProject() }
|
|
77
|
-
.keyboardShortcut(.defaultAction)
|
|
78
|
-
.disabled(newProjectName.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
.padding(14)
|
|
82
|
-
}
|
|
83
|
-
}) {
|
|
84
|
-
if store.projects.isEmpty {
|
|
85
|
-
Text("No projects")
|
|
86
|
-
.font(.caption).foregroundStyle(.white.opacity(0.5)).padding(.leading, 6)
|
|
87
|
-
}
|
|
88
|
-
ForEach(store.projects) { project in
|
|
89
|
-
filterRow(.project(project.id), icon: "folder", label: project.name)
|
|
90
|
-
.disabled(!store.projectStore.canMutateProjects)
|
|
91
|
-
.contextMenu {
|
|
92
|
-
Button("Delete", role: .destructive) {
|
|
93
|
-
do {
|
|
94
|
-
try store.projectStore.removeProject(id: project.id)
|
|
95
|
-
} catch {
|
|
96
|
-
store.operationError = store.projectStore.persistenceError ?? error.localizedDescription
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private func commitNewProject() {
|
|
105
|
-
let name = newProjectName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
106
|
-
guard !name.isEmpty else { return }
|
|
107
|
-
Task {
|
|
108
|
-
do {
|
|
109
|
-
try await store.projectStore.addProject(name: name)
|
|
110
|
-
addingProject = false
|
|
111
|
-
newProjectName = ""
|
|
112
|
-
} catch {
|
|
113
|
-
store.operationError = store.projectStore.persistenceError
|
|
114
|
-
?? (error as? RecordingsCLI.Failure)?.message
|
|
115
|
-
?? error.localizedDescription
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// MARK: - Modes
|
|
121
|
-
|
|
122
|
-
private var hasAnyMode: Bool {
|
|
123
|
-
store.count(for: .mode("enhanced")) > 0 || store.count(for: .mode("raw")) > 0
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private var modesSection: some View {
|
|
127
|
-
section(title: "Modes") {
|
|
128
|
-
if store.count(for: .mode("enhanced")) > 0 {
|
|
129
|
-
filterRow(.mode("enhanced"), icon: "wand.and.stars", label: "Enhanced")
|
|
130
|
-
}
|
|
131
|
-
if store.count(for: .mode("raw")) > 0 {
|
|
132
|
-
filterRow(.mode("raw"), icon: "text.quote", label: "Raw")
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// MARK: - Machines
|
|
138
|
-
|
|
139
|
-
private var machinesSection: some View {
|
|
140
|
-
section(title: "Machines") {
|
|
141
|
-
if store.count(for: .thisMachine) > 0 {
|
|
142
|
-
filterRow(.thisMachine, icon: "desktopcomputer", label: "This Mac")
|
|
143
|
-
}
|
|
144
|
-
ForEach(otherMachines, id: \.self) { machine in
|
|
145
|
-
filterRow(.machine(machine), icon: "macpro.gen3.server", label: machine)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private var otherMachines: [String] {
|
|
151
|
-
store.machines.filter { $0 != store.localMachineID }
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// MARK: - Building blocks
|
|
155
|
-
|
|
156
|
-
@ViewBuilder
|
|
157
|
-
private func section<Trailing: View, Content: View>(
|
|
158
|
-
title: String,
|
|
159
|
-
@ViewBuilder trailing: () -> Trailing = { EmptyView() },
|
|
160
|
-
@ViewBuilder content: () -> Content
|
|
161
|
-
) -> some View {
|
|
162
|
-
VStack(alignment: .leading, spacing: 6) {
|
|
163
|
-
HStack {
|
|
164
|
-
Text(title.uppercased())
|
|
165
|
-
.font(.caption2.weight(.semibold))
|
|
166
|
-
.foregroundStyle(.white.opacity(0.6))
|
|
167
|
-
.padding(.leading, 6)
|
|
168
|
-
Spacer()
|
|
169
|
-
trailing().foregroundStyle(.white.opacity(0.85)).padding(.trailing, 6)
|
|
170
|
-
}
|
|
171
|
-
content()
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private func filterRow(_ target: LibraryFilter, icon: String, label: String) -> some View {
|
|
176
|
-
let selected = store.pane == .library && store.filter == target
|
|
177
|
-
let count = store.count(for: target)
|
|
178
|
-
return Button {
|
|
179
|
-
withAnimation(.spring(response: 0.32, dampingFraction: 0.82)) {
|
|
180
|
-
store.filter = target
|
|
181
|
-
store.pane = .library
|
|
182
|
-
if !store.visibleRecordings.contains(where: { $0.id == store.selection }) {
|
|
183
|
-
store.selection = store.visibleRecordings.first?.id
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
} label: {
|
|
187
|
-
rowLabel(icon: icon, label: label, count: count > 0 ? count : nil, selected: selected, accentDot: false)
|
|
188
|
-
}
|
|
189
|
-
.buttonStyle(.plain)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private func rowLabel(icon: String, label: String, count: Int?, selected: Bool, accentDot: Bool) -> some View {
|
|
193
|
-
HStack(spacing: 9) {
|
|
194
|
-
Image(systemName: icon).font(.system(size: 12, weight: .medium)).frame(width: 16)
|
|
195
|
-
Text(label).font(.system(.subheadline, design: .rounded)).lineLimit(1)
|
|
196
|
-
Spacer(minLength: 4)
|
|
197
|
-
if accentDot {
|
|
198
|
-
Circle().fill(Theme.recordRed).frame(width: 7, height: 7)
|
|
199
|
-
} else if let count {
|
|
200
|
-
Text("\(count)").font(.caption2.monospacedDigit()).foregroundStyle(.white.opacity(0.55))
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
.foregroundStyle(.white.opacity(selected ? 1 : 0.85))
|
|
204
|
-
.padding(.horizontal, 9).padding(.vertical, 7)
|
|
205
|
-
.contentShape(Rectangle())
|
|
206
|
-
.background {
|
|
207
|
-
if selected {
|
|
208
|
-
RoundedRectangle(cornerRadius: Theme.cornerSmall, style: .continuous).fill(.white.opacity(0.22))
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|