Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .agents/skills/qa-critical-ux/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ configuration (`VITE_APP_URL`, `VITE_API_URL`, `VITE_SUPABASE_URL`,
`VITE_SUPABASE_ANON_KEY` must match production; local URLs cannot pass), uses
its own persistent Cargo cache under `~/Library/Caches/anarlog` (never the
repo `target` directory), and launches with `AUDIO_SYNC_PROBE=1` and
`LISTENER_DEBUG=1`. Reuse an already-current bundle with `--launch-only`.
`LISTENER_DEBUG=1`. It launches the `.app` through LaunchServices so macOS
evaluates process-scoped permissions against the bundle identity. Do not run
the executable under `Contents/MacOS` directly. Reuse an already-current
bundle with `--launch-only`.

Pin release-gate builds to the intended candidate commit:

Expand Down
23 changes: 23 additions & 0 deletions .agents/skills/qa-critical-ux/scripts/launch-native-dev-qa.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash

set -euo pipefail

[[ $# -eq 1 ]] || {
echo "Usage: $0 <app-bundle>" >&2
exit 2
}

qa_bundle_dir="$1"
qa_open_executable="${ANARLOG_QA_OPEN_EXECUTABLE:-/usr/bin/open}"
qa_open_args=(
-W
--env AUDIO_SYNC_PROBE=1
--env LISTENER_DEBUG=1
--env NO_AEC=
)

if [[ -n "${ONBOARDING+x}" ]]; then
qa_open_args+=(--env "ONBOARDING=$ONBOARDING")
fi

exec "$qa_open_executable" "${qa_open_args[@]}" "$qa_bundle_dir"
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { chmod, mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";

const launcher = fileURLToPath(
new URL("./launch-native-dev-qa.sh", import.meta.url),
);

async function fixture() {
const directory = await mkdtemp(path.join(tmpdir(), "anarlog-native-qa-"));
const appBundle = path.join(directory, "Anarlog Dev.app");
const fakeOpen = path.join(directory, "open");

await mkdir(appBundle);
await writeFile(fakeOpen, "#!/usr/bin/env bash\nprintf '%s\\n' \"$@\"\n");
await chmod(fakeOpen, 0o755);

return {
appBundle,
directory,
launch(environment = {}) {
const childEnvironment = {
...process.env,
ANARLOG_QA_OPEN_EXECUTABLE: fakeOpen,
...environment,
};
if (!("ONBOARDING" in environment)) {
delete childEnvironment.ONBOARDING;
}

return spawnSync(launcher, [appBundle], {
encoding: "utf8",
env: childEnvironment,
});
},
};
}

test("launches the app bundle through LaunchServices with QA probes", async (t) => {
const harness = await fixture();
t.after(() => rm(harness.directory, { force: true, recursive: true }));

const result = harness.launch();

assert.equal(result.status, 0, result.stderr);
assert.deepEqual(result.stdout.trim().split("\n"), [
"-W",
"--env",
"AUDIO_SYNC_PROBE=1",
"--env",
"LISTENER_DEBUG=1",
"--env",
"NO_AEC=",
harness.appBundle,
]);
});

test("forwards the onboarding reset through LaunchServices", async (t) => {
const harness = await fixture();
t.after(() => rm(harness.directory, { force: true, recursive: true }));

const result = harness.launch({ ONBOARDING: "1" });

assert.equal(result.status, 0, result.stderr);
assert.deepEqual(result.stdout.trim().split("\n").slice(-3), [
"--env",
"ONBOARDING=1",
harness.appBundle,
]);
});
6 changes: 4 additions & 2 deletions .agents/skills/qa-critical-ux/scripts/run-native-dev-qa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ qa_lock_dir="$qa_target_dir/.anarlog-native-dev-qa-lock"
qa_frontend_dist="$qa_repo_root/apps/desktop/dist"
qa_config_validator="$qa_repo_root/.agents/skills/qa-critical-ux/scripts/validate-native-dev-qa-config.mjs"
qa_gitbutler_candidate_resolver="$qa_repo_root/.agents/skills/qa-critical-ux/scripts/resolve-gitbutler-candidate.mjs"
qa_launcher="$qa_repo_root/.agents/skills/qa-critical-ux/scripts/launch-native-dev-qa.sh"
qa_release_workflow="$qa_repo_root/.github/workflows/desktop_cd.yaml"
qa_expected_supabase_url="https://ijoptyyjrfqwaqhyxkxj.supabase.co"
qa_xcode_developer_dir="/Applications/Xcode.app/Contents/Developer"
Expand Down Expand Up @@ -490,6 +491,8 @@ validate_manifest() {
fail "Compiled frontend validator not found: $qa_config_validator"
[[ -f "$qa_gitbutler_candidate_resolver" ]] ||
fail "GitButler candidate resolver not found: $qa_gitbutler_candidate_resolver"
[[ -x "$qa_launcher" ]] ||
fail "Native Dev QA launcher not found: $qa_launcher"
qa_node_executable="$(command -v node)" ||
fail "Node.js is required for native Dev QA configuration validation."
qa_git_executable="$(command -v git)" ||
Expand Down Expand Up @@ -681,5 +684,4 @@ cleanup
trap - EXIT INT TERM

cd "$qa_repo_root"
exec env -u NO_AEC AUDIO_SYNC_PROBE=1 LISTENER_DEBUG=1 \
"$qa_bundle_executable"
exec "$qa_launcher" "$qa_bundle_dir"
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
python3 .github/scripts/check_license_boundary.py
- run: >-
node --test
.agents/skills/qa-critical-ux/scripts/launch-native-dev-qa.test.mjs
scripts/desktop-release-provenance.test.mjs
scripts/publish-anarlog-skill.test.mjs
scripts/repack-linux-appimage.test.mjs
Expand Down
Loading