Skip to content

Harden simulator launch-arg tooling against stale nav routes - #101

Open
saksham2001 wants to merge 1 commit into
mainfrom
claude/zen-maxwell-nk8e9u
Open

Harden simulator launch-arg tooling against stale nav routes#101
saksham2001 wants to merge 1 commit into
mainfrom
claude/zen-maxwell-nk8e9u

Conversation

@saksham2001

Copy link
Copy Markdown
Owner

Summary

Launching the simulator with -seedDemo YES -openWorkout YES could leave the root NavigationPath holding duplicate/stale AppRoute.activityDetail(id) entries, which made the Back button look dead — each pop landed on another identical detail page, and entries whose session had been wiped by a later reseed rendered as a near-blank screen.

Two causes, both in DEBUG/simulator tooling paths:

  1. The root .task in RootAppView re-fires when the root Group swaps content (onboarding → main tabs), so the reseed-and-push block could run more than once per process. The second pass's SeedData.clearAll then wiped the sessions that already-pushed routes pointed at.
  2. The openWorkout handler appended onto whatever was already on the stack, so stale copies accumulated.

Changes:

  • PulseLoop/Views/RootViews.swift — guard the launch-arg block behind a new didRunLaunchArgs @State flag so reseed + push run at most once per process. liveWorkout.recover() and routeDeepLinkIfNeeded() deliberately stay outside the guard so they still run on every task pass (scene re-activation, Live Activity deep links).
  • PulseLoop/Views/RootViews.swift — reset path to a fresh NavigationPath() before pushing the openWorkout route, so Back always returns to the dashboard rather than to a leftover detail route.
  • PulseLoop/Views/RecordViews.swift — replace ActivityDetailView's one-line EmptyStateView fallback with a full-screen ContentUnavailableView (icon, description, and an explicit Back to Dashboard button), so a stale id is never a blank dead-end page.

No production flow changes: the guarded block only runs for the seedDemo / openWorkout / openRecord launch args, and the ActivityDetailView fallback is the pre-existing missing-session branch, just made usable.

Related issues

N/A — found while smoke-testing with demo-data launch args.

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 📟 New / improved wearable support (BLE driver layer)
  • 🤖 Coach / LLM change (tools, prompts, orchestration)
  • 🎨 UI / DesignSystem change
  • 🧹 Refactor / chore (no behavior change)
  • 📝 Docs only
  • ⚠️ Breaking change (existing data, settings, or APIs change)

How was this tested?

  • Added / updated unit tests (PulseLoopTests)
  • Ran the test suite locally (⌘U in Xcode)
  • Tested on a physical device with a real ring — model: N/A (no BLE code touched)
  • Tested with demo data (-seedDemo YES, no hardware)
  • N/A (docs / non-code change)

Note: the patch was originally authored in an environment without Xcode and was never run. All simulator verification below was done separately by the agent that opened this PR, not by the original author.

Verified on iPhone 16 Pro (iOS 18.6), Debug build:

  • Full suite green: Executed 756 tests, with 0 failures (** TEST SUCCEEDED **).
  • A/B against the pre-fix code on the identical scenario (fresh install, so the store starts with no profile and the root Group genuinely swaps onboarding → main tabs): with the guard reverted, -seedDemo YES -openWorkout YES needed two or more Back taps and still sat on an identical detail page — the reported symptom. With the fix, one Back tap lands on the dashboard.
  • Repeated relaunches with -seedDemo YES -openWorkout YES (5 process launches): detail opens each time, one Back tap → dashboard.
  • Background (HOME) and foreground the same process with the flags still set: still on the detail page with its data intact (no second reseed), one Back tap → dashboard.
  • Missing-session fallback: rendered ActivityDetailView(sessionId:) against an empty store with an id that resolves to nothing — shows the full-screen "Workout not found" card with the Back to Dashboard action instead of a blank page (screenshot below).
  • SwiftLint: no new violations except one line_length (143 > 140) on the openWorkout condition line, caused purely by the extra indentation of the guarded block. The SwiftLint workflow does not run --strict, so this annotates but does not fail CI.

Privacy & data

  • This change does not send health data off-device without explicit user action.
  • No secrets, API keys, or personal data are committed.
  • N/A

Screenshots / recordings

Missing-session fallback (ContentUnavailableView), rendered against an empty store — a centred figure.run.circle icon, the Workout not found title, the "no longer in local storage" description, and an accent-coloured Back to Dashboard button, filling the screen on PulseColors.background. (Image to be attached; it was captured locally rather than uploaded by the automation that opened this PR.)

Checklist

  • My code follows the project's style (SwiftLint passes).
  • I ran the tests and they pass.
  • I updated docs / README where relevant.
  • I read the Contributing guide.

Launching with -seedDemo YES -openWorkout YES could leave the
NavigationPath holding duplicate/stale activityDetail entries: the
root .task re-fires when the root Group swaps content (onboarding to
main tabs), so reseed + push could run more than once per process,
and a later SeedData.clearAll wiped the sessions that earlier route
entries pointed at. Back then appeared dead because pops landed on
identical stale copies.

- Guard the launch-arg block with a didRunLaunchArgs @State flag so
  reseed + push runs at most once per process; liveWorkout.recover()
  and deep-link routing still run on every task pass.
- Reset the NavigationPath before pushing the openWorkout route so
  Back always returns to the dashboard.
- Upgrade ActivityDetailView's missing-session fallback to a
  full-screen ContentUnavailableView with an explicit Back to
  Dashboard action, so a stale id is never a dead-end page.

Only affects DEBUG/simulator tooling paths, not production flows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant