Skip to content

fix(iOS): port minute-block chime behavior from web#177

Merged
auerbachb merged 2 commits into
mainfrom
issue-78-ios-chime-block-fill
Apr 18, 2026
Merged

fix(iOS): port minute-block chime behavior from web#177
auerbachb merged 2 commits into
mainfrom
issue-78-ios-chime-block-fill

Conversation

@auerbachb
Copy link
Copy Markdown
Owner

@auerbachb auerbachb commented Apr 18, 2026

Closes #78

Summary

  • port the web minute-block chime logic to iOS so chimes trigger on completed visual minute blocks instead of clock-minute boundaries
  • keep minute-block progress tracking independent from mute state, and seed progress on resume so completed chimes are not replayed
  • add StillPointShared regression tests covering 4:20 and 5:00 sequences, <1 minute/final-minute exclusions, <=120s sessions, mute independence, resume seeding, and fresh-start reset

Manual test matrix

  • Chime fires when each minute block completes (aligned to visual grid, not clock minutes)
  • For 4:20 session: chimes at 3:20, 2:20, 1:20 remaining (no chime at 0:20)
  • For exact 5:00 session: chimes at 4:00, 3:00, 2:00, 1:00 (no behavioral change)
  • No chime when less than 1 minute remains
  • No chime during final-minute 10-second blocks
  • Sessions <= 120s: no minute-block chimes
  • Block progress tracking works even when chimes are muted
  • Pause/resume doesn't replay completed block chimes
  • Fresh session start doesn't inherit previous session's elapsed state

Validation run

  • xcodebuild -scheme StillPointShared -destination 'generic/platform=iOS' build
  • Device/simulator manual verification for the matrix above

Notes

  • swift test on this machine compiles the package for macOS host and fails in AudioEngine due to iOS-only AVFoundation APIs; iOS scheme build passes.

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Improved chime triggering for more consistent, predictable in-session audio cues.
    • Enhanced resume behavior to correctly preserve elapsed time and chime state.
    • Added macOS support with platform-appropriate audio handling.
  • Tests

    • Added comprehensive unit tests covering minute-chime behavior and session timing.

Port the web minute-boundary chime behavior so iOS chimes follow completed minute blocks, skip final-minute blocks, and never replay after mute toggles or resume.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
still-point Ignored Ignored Preview Apr 18, 2026 9:55pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

Port of minute-block chime behavior from web to iOS: adds SessionLogic minute-block APIs, updates SessionViewModel to track completed minute blocks (seeded correctly on resume vs fresh start) and call new chime update API per tick, adds macOS-safe AudioEngine stubs, and introduces unit tests and SPM test target.

Changes

Cohort / File(s) Summary
Minute-block logic & tests
ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift, ios/StillPointShared/Tests/StillPointSharedTests/SessionLogicMinuteChimeTests.swift
Added public minute-block helper APIs (usesMinuteBlocks, minuteBlockCount, completedMinuteBlockIndex, chimeCountForCompletedMinuteBlock, MinuteChimeUpdate, nextMinuteChimeUpdate) and unit tests validating chime boundaries, mute/seed behavior, pause/resume, and sessions ≤120s. Refactored block-building/statusLabel to use these helpers.
SessionViewModel chime integration
ios/StillPointApp/ViewModels/SessionViewModel.swift
Replaced lastChimeMinutesLeft with lastCompletedMinuteBlockIndex (init -1). start() now seeds differently for resume vs fresh start; per-tick logic calls SessionLogic.nextMinuteChimeUpdate(...) and only plays chimes when the completed-block index advances (respecting sound prefs and optional chime counts).
Audio + platform support
ios/StillPointShared/Sources/StillPointShared/AudioEngine.swift, ios/StillPointShared/Package.swift
Added macOS platform to Package.swift and a macOS conditional AudioEngine with no-op playback methods and same prefs API; retained AVFoundation-based implementation for non-macOS. Added SPM test target StillPointSharedTests.

Sequence Diagram(s)

sequenceDiagram
    participant UI as "UI / Timer UI"
    participant VM as "SessionViewModel"
    participant Logic as "SessionLogic"
    participant Audio as "AudioEngine"
    UI->>VM: start / resume / tick events
    VM->>Logic: completedMinuteBlockIndex(elapsed,totalSeconds) (seed on resume)
    VM->>Logic: nextMinuteChimeUpdate(elapsed,totalSeconds,lastCompletedBlockIndex) each tick
    alt completed block index advanced
        VM->>Audio: playChime(count) [if soundPrefs.chime]
        VM->>VM: update lastCompletedMinuteBlockIndex
    else no advancement
        VM-->>UI: update UI (no chime)
    end
    VM->>Audio: playTick() per-second (if enabled)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A hop, a tick, a gentle chime aligned,
Blocks finish neat — the old clock-ticks resigned.
Resume keeps its place, fresh starts begin true,
Silent or ringing, the blocks now know what to do.
A rabbit cheers — code hopped through!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(iOS): port minute-block chime behavior from web' accurately summarizes the main objective of porting web chime logic to iOS and fixing the chime timing behavior.
Linked Issues check ✅ Passed All acceptance criteria from issue #78 are met: chimes fire at block completion boundaries, 4:20 and 5:00 sequences correct, sessions ≤120s excluded, mute independence verified, pause/resume seeding prevents replays, and fresh start doesn't inherit elapsed state.
Out of Scope Changes check ✅ Passed All changes directly support the minute-block chime port: SessionViewModel updates chime triggering, SessionLogic adds minute-block helpers, AudioEngine supports macOS testing, and Package.swift enables test infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #78

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-78-ios-chime-block-fill

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift (1)

49-50: Keep the remaining-minute math integer-only.

blockEnd and totalSeconds are already Int, so converting to Double here reintroduces floating-point boundary math in the middle of a port that is explicitly trying to avoid it. Integer division preserves the current behavior and keeps this calculation exact.

Proposed change
         guard blockIndex >= 0 else { return nil }
         let blockEnd = (blockIndex + 1) * 60
-        let chimeCount = Int(floor(Double(totalSeconds - blockEnd) / 60.0))
+        let chimeCount = (totalSeconds - blockEnd) / 60
         return chimeCount >= 1 ? chimeCount : nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift` around
lines 49 - 50, The calculation for chimeCount introduces floating-point math by
casting to Double and using floor; replace this with integer-only arithmetic to
preserve exactness: compute blockEnd as (blockIndex + 1) * 60 and set chimeCount
using integer division on (totalSeconds - blockEnd) (e.g., (totalSeconds -
blockEnd) / 60), taking care to handle negative results if necessary; update the
code referencing blockEnd, blockIndex, totalSeconds, and chimeCount accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/StillPointShared/Package.swift`:
- Around line 15-18: The test target is untestable on macOS because the package
declares iOS/watchOS platforms while AudioEngine.swift imports AVFoundation
directly; either gate the AVFoundation usage with conditional compilation in
AudioEngine.swift (e.g., wrap AVFoundation import and AV-specific types with `#if`
canImport(AVFoundation) / `#endif`) or extract the AVFoundation-dependent code
into a separate iOS-only target in Package.swift and have the main
StillPointShared target contain only platform-neutral code; update the
.testTarget to depend on the platform-neutral StillPointShared core target (or
add tests for the core target) so swift test runs on CI without requiring
iOS-only APIs.

---

Nitpick comments:
In `@ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift`:
- Around line 49-50: The calculation for chimeCount introduces floating-point
math by casting to Double and using floor; replace this with integer-only
arithmetic to preserve exactness: compute blockEnd as (blockIndex + 1) * 60 and
set chimeCount using integer division on (totalSeconds - blockEnd) (e.g.,
(totalSeconds - blockEnd) / 60), taking care to handle negative results if
necessary; update the code referencing blockEnd, blockIndex, totalSeconds, and
chimeCount accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ddaa812-1430-4839-bcf1-0e9cb2d26250

📥 Commits

Reviewing files that changed from the base of the PR and between 8e93f7f and 9ba92a6.

📒 Files selected for processing (4)
  • ios/StillPointApp/ViewModels/SessionViewModel.swift
  • ios/StillPointShared/Package.swift
  • ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift
  • ios/StillPointShared/Tests/StillPointSharedTests/SessionLogicMinuteChimeTests.swift

Comment thread ios/StillPointShared/Package.swift
Use integer division for minute-block chime counts to match web semantics without float boundaries. Gate AVFoundation behind non-macOS so swift test can compile on macOS hosts; declare macOS v14 in the package manifest.

Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
ios/StillPointShared/Sources/StillPointShared/AudioEngine.swift (1)

15-37: Consider de-duplicating sound prefs logic across compile branches.

SoundPrefs, prefsKey, loadPrefs(), and savePrefs(_:) are duplicated in both branches. This is easy to drift over time; extracting shared prefs code outside the #if/#else block would reduce maintenance risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/StillPointShared/Sources/StillPointShared/AudioEngine.swift` around lines
15 - 37, The SoundPrefs struct and its persistence helpers are duplicated across
compile branches; remove the duplicates by extracting the shared declarations
(SoundPrefs, prefsKey, loadPrefs(), savePrefs(_:) ) out of the `#if/`#else blocks
and into a single common section so both branches use the same types and
functions; keep any branch-specific behavior inside the conditional blocks but
reference the unified SoundPrefs and its methods from there to avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ios/StillPointShared/Sources/StillPointShared/AudioEngine.swift`:
- Around line 15-37: The SoundPrefs struct and its persistence helpers are
duplicated across compile branches; remove the duplicates by extracting the
shared declarations (SoundPrefs, prefsKey, loadPrefs(), savePrefs(_:) ) out of
the `#if/`#else blocks and into a single common section so both branches use the
same types and functions; keep any branch-specific behavior inside the
conditional blocks but reference the unified SoundPrefs and its methods from
there to avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1d335b96-24a4-446f-96bd-2d5ff750b6c7

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba92a6 and 15c0e1d.

📒 Files selected for processing (3)
  • ios/StillPointShared/Package.swift
  • ios/StillPointShared/Sources/StillPointShared/AudioEngine.swift
  • ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • ios/StillPointShared/Package.swift
  • ios/StillPointShared/Sources/StillPointShared/SessionLogic.swift

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.

iOS: Port chime-on-block-fill fix from web (PR #73)

2 participants