Skip to content
Merged
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/qa-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ test_layers:
deep: "bash scripts/ops/transcripted-qa-bench.sh --mode deep"
full: "bash scripts/ops/transcripted-qa-bench.sh --mode full"
ui: "bash scripts/ops/transcripted-qa-bench.sh --mode ui"
sparkle_update: "bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update"
packaged: "bash scripts/ops/transcripted-qa-bench.sh --mode packaged"
artifact: "bash scripts/ops/transcripted-qa-bench.sh --mode artifact"
audio_synthetic: "bash scripts/ops/transcripted-qa-bench.sh --mode audio-synthetic"
corpus: "bash scripts/ops/transcripted-qa-bench.sh --mode corpus"
corpus_compare: "bash scripts/ops/transcripted-qa-bench.sh --mode corpus-compare"
live: "bash scripts/ops/transcripted-qa-bench.sh --mode live"
proves:
- "one local QA report can combine build, fast tests, E2E, integration, QA CLI, synthetic audio, release-health, packaged-app, optional Gemma planning, corpus, and live capture proof"
- "one local QA report can combine build, fast tests, E2E, integration, QA CLI, Sparkle update UI smoke, synthetic audio, release-health, packaged-app, optional Gemma planning, corpus, and live capture proof"
agent_notes:
- "Reports live under /tmp/transcripted-qa-bench/<run-id>/qa-report.md."
- "Corpus and live modes depend on private local data, permissions, and hardware. Permission blockers are INCOMPLETE, not green."
Expand Down Expand Up @@ -107,6 +108,7 @@ test_layers:
release_candidate_report: "python3 scripts/ops/release-gate-report.py --release-candidate"
packaging_smoke: "SKIP_NOTARIZATION=1 bash build-beta.sh '' <user-name>"
packaged_app_smoke: "bash scripts/ops/transcripted-qa-bench.sh --mode packaged"
sparkle_update_smoke: "bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update"
sparkle_verify: "bash scripts/release/verify-sparkle-release.sh <version>"
sentry_register: "SENTRY_REQUIRE_DEBUG_FILES=1 bash scripts/release/register-sentry-release.sh <version>"
cask_update: "bash scripts/release/update-cask.sh <version>"
Expand Down Expand Up @@ -171,6 +173,7 @@ recommended_gates:
- "swift test"
- "SKIP_NOTARIZATION=1 bash build-beta.sh '' <user-name>"
- "bash scripts/ops/transcripted-qa-bench.sh --mode packaged"
- "bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update"
add_when_shipping_to_users:
- "bash scripts/ops/transcripted-qa-bench.sh --mode deep --strict-artifacts when fixture or private artifact roots are available"
- "full notarized build-beta.sh"
Expand Down
39 changes: 38 additions & 1 deletion Sources/Observability/SparkleUpdaterController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,49 @@ final class SparkleUpdaterController: NSObject, ObservableObject {

override init() {
super.init()
guard !Self.isLaunchUISmoke else { return }
guard !Self.isLaunchUISmoke else {
applyLaunchUISmokeUpdateStateIfPresent()
return
}
trackInstalledUpdateIfNeeded()
observeUpdaterReadiness()
observeUpdaterSettings()
}

private func applyLaunchUISmokeUpdateStateIfPresent() {
guard let state = Self.launchUISmokeUpdateState() else { return }
updateStatus = UpdateStatus(state: state, canCheckForUpdates: true)
automaticUpdateSettings = AutomaticUpdateSettings(
automaticChecksEnabled: true,
automaticDownloadsAllowed: true,
automaticDownloadsEnabled: false
)
}

nonisolated private static func launchUISmokeUpdateState() -> UpdateStatus.State? {
let environment = ProcessInfo.processInfo.environment
guard let rawState = environment["TRANSCRIPTED_LAUNCH_UI_SMOKE_UPDATE_STATE"]?
.trimmingCharacters(in: .whitespacesAndNewlines)
.lowercased(),
!rawState.isEmpty else {
return nil
}
let version = environment["TRANSCRIPTED_LAUNCH_UI_SMOKE_UPDATE_VERSION"]?
.trimmingCharacters(in: .whitespacesAndNewlines)
let displayVersion = version?.isEmpty == false ? version! : "9.9.9"

switch rawState {
case "available", "update-available":
return .updateAvailable(version: displayVersion)
case "downloading", "download-progress":
return .downloading(version: displayVersion)
case "ready", "ready-to-install":
return .readyToInstall(version: displayVersion)
default:
return nil
}
}

func performStartupUpdateCheckIfNeeded() {
guard !Self.isLaunchUISmoke else { return }
guard !hasPerformedStartupCheck else { return }
Expand Down
2 changes: 2 additions & 0 deletions Sources/UI/MenuBar/MenuBarContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import AppKit

struct MenuBarContentSmokeSnapshot: Codable, Equatable {
let header: MenuBarHeaderSmokeSnapshot
let updateCallout: MenuBarActionRowSmokeSnapshot
let primaryActions: [String: MenuBarActionRowSmokeSnapshot]
let utilityActions: [String: MenuBarActionRowSmokeSnapshot]
}
Expand Down Expand Up @@ -175,6 +176,7 @@ final class MenuBarContentView: NSView {
var smokeSnapshot: MenuBarContentSmokeSnapshot {
MenuBarContentSmokeSnapshot(
header: headerView.smokeSnapshot,
updateCallout: updateCalloutRow.smokeSnapshot,
primaryActions: primaryActionsView.smokeSnapshot,
utilityActions: utilityActionsView.smokeSnapshot
)
Expand Down
8 changes: 8 additions & 0 deletions Sources/UI/MenuBar/MenuBarPanelController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ final class MenuBarPanelController: NSViewController {
warningText: "",
isReady: false
),
updateCallout: MenuBarActionRowSmokeSnapshot(
title: "",
detail: "",
trailingText: "",
automationIdentifier: "",
isVisible: false,
isEnabled: false
),
primaryActions: [:],
utilityActions: [:]
)
Expand Down
22 changes: 19 additions & 3 deletions Tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Test Surfaces

This repo has ten distinct verification layers:
This repo has eleven distinct verification layers:

1. `bash run-tests.sh`
Curated fast test runner built with raw `swiftc`
Expand All @@ -20,9 +20,11 @@ This repo has ten distinct verification layers:
Local hardware/TCC smoke for app launch plus production mic + system-audio capture
8. `bash scripts/ops/transcripted-qa-bench.sh --mode ui`
Accessibility-driven UI smoke for first-run onboarding, menu bar, Home, Settings, buttons, and basic navigation
9. `bash scripts/ops/transcripted-qa-bench.sh --mode packaged`
9. `bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update`
No-publish fake-state Sparkle update UI smoke for update-available and downloading menu surfaces
10. `bash scripts/ops/transcripted-qa-bench.sh --mode packaged`
No-publish `build-beta.sh` package smoke plus built app version, Sparkle, signing, dSYM, DMG, optional menu bar, and local log privacy checks
10. `bash scripts/ops/transcripted-qa-bench.sh --mode full`
11. `bash scripts/ops/transcripted-qa-bench.sh --mode full`
Deep QA plus release-health fixture proof and local Gemma summary planning when eligible transcripts exist

There is also an orchestrated QA bench for human-style passes:
Expand All @@ -32,6 +34,7 @@ bash scripts/ops/transcripted-qa-bench.sh --mode quick
bash scripts/ops/transcripted-qa-bench.sh --mode deep
bash scripts/ops/transcripted-qa-bench.sh --mode full
bash scripts/ops/transcripted-qa-bench.sh --mode ui
bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update
bash scripts/ops/transcripted-qa-bench.sh --mode packaged
bash scripts/ops/transcripted-qa-bench.sh --mode pasteback-synthetic
bash scripts/ops/transcripted-qa-bench.sh --mode corpus
Expand Down Expand Up @@ -189,6 +192,19 @@ Accessibility permission for the terminal or Codex runner so it can inspect AX
identifiers and press controls. Missing permission exits `3` and is reported as
`INCOMPLETE`, not green.

## Sparkle Update UI Smoke

`bash scripts/ops/transcripted-qa-bench.sh --mode sparkle-update` builds the
app, then runs `transcripted-qa sparkle-update-smoke` against
`build/Transcripted.app`. It launches the app through the launch-smoke harness
with fake update-available and downloading states, then validates the menu
snapshot copy and visibility for the prominent install callout and disabled
download-progress row.

This is no-publish local UI proof only. It does not contact the live appcast,
download or verify an update, install, relaunch, notarize, publish, update
Homebrew, or prove an existing installed app can upgrade.

## Packaged App Smoke

`bash scripts/ops/transcripted-qa-bench.sh --mode packaged` runs a no-publish
Expand Down
2 changes: 1 addition & 1 deletion Tests/RepoCommandContractTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func testRepoCommandContract() {
let matrix = readRepoTextFile(".agents/test-matrix.yml")

assertTrue(
qaBench.contains("quick|deep|full|ui|imported-audio-native|packaged|artifact|audio-synthetic|pasteback-synthetic|corpus|corpus-compare|scorecard|live")
qaBench.contains("quick|deep|full|ui|imported-audio-native|sparkle-update|packaged|artifact|audio-synthetic|pasteback-synthetic|corpus|corpus-compare|scorecard|live")
&& qaBench.contains("run_full_tail")
&& qaBench.contains("60-release-health")
&& qaBench.contains("61-gemma-summary-plan")
Expand Down
Loading
Loading