Skip to content

feat: add on-call billing tracking#18

Merged
wiseelf merged 8 commits into
mainfrom
feature/oncall-billing
Jun 26, 2026
Merged

feat: add on-call billing tracking#18
wiseelf merged 8 commits into
mainfrom
feature/oncall-billing

Conversation

@wiseelf

@wiseelf wiseelf commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds on-call billing tracking as a 4th tab (On-call) in the menu bar app
  • Introduces TimeTrackerCore library split for pure, testable billing logic
  • Passive on-call time (billable window minus active sessions) billed at configurable rate (default 40%); active on-call sessions billed at 100%

What changed

Architecture

  • Extracted TimeTrackerCore as an SPM library target; TimeTracker executable depends on it
  • TimeSession, Extensions moved to TimeTrackerCore with public visibility
  • @_exported import TimeTrackerCore in CoreImport.swift preserves backward compat for existing files

Models (OnCallModels.swift)

  • OnCallRotationBlock: date-ranged blocks with per-day-of-week schedules
  • OnCallSettings: passive/active multipliers, non-billable rules, rate history
  • DaySchedule, NonBillableRule, RateEntry

Billing logic (OnCallBilling.swift)

  • Pure interval arithmetic: mergeRanges, subtractRanges
  • billableMinutes, activeMinutesWithinBillable, passiveMinutes, rate — all static, no AppKit deps

Store & timer

  • OnCallStore: persists rotations + settings to ~/Library/Application Support/TimeTracker/oncall.json
  • TimerManager: startOnCallActive() splits running session (saves elapsed as regular, restarts tagged isOnCallActive=true); isRunningOnCall published for UI

UI

  • OnCallView / OnCallSettingsSection / OnCallRotationListView / OnCallSummaryView
  • ContentView: orange timer border when on-call active; on-call toggle button in timer row
  • StatsView: orange bar overlay for on-call active sessions; income footer when tracking enabled

Tests — 21 Swift Testing tests covering all billing functions; swift-testing added as package dep

Breaking changes

None. isOnCallActive uses decodeIfPresent — existing sessions.json files decode without changes.

Test plan

  • swift test — all 21 on-call billing tests pass
  • swift build -c release — clean build
  • Add a rotation, verify summary week/month view shows passive hours
  • Start timer → click "On-call" button → verify session splits and isOnCallActive session recorded
  • Enable income tracking, verify income footer appears in Stats and On-call summary
  • Import existing sessions.json — verify no decode errors

@wiseelf wiseelf force-pushed the feature/oncall-billing branch from dc8033e to f90413c Compare June 26, 2026 09:41
wiseelf added 8 commits June 26, 2026 19:44
- Library split: TimeTrackerCore (pure, testable) + TimeTracker executable
- OnCallModels: rotation blocks, day schedules, non-billable rules, rate history
- OnCallBilling: interval arithmetic for billable/passive/active minutes + rate lookup
- OnCallStore: persistent on-call configuration and rotations
- TimerManager: startOnCallActive() splits session at click → isRunningOnCall flag
- ContentView: 4th On-call tab + orange On-call button in timer
- StatsView: orange markers for active on-call sessions + income footer
- OnCallView: settings, rotation list, week/month summary
- 21 Swift Testing tests for billing logic (all passing)
- Move rotation list inside week/month period section (OnCallSummaryView)
- Filter rotations to only those overlapping the selected period
- Allow forward navigation to future weeks/months
- Replace Quit with Back button when settings page is open
@wiseelf wiseelf force-pushed the feature/oncall-billing branch from f90413c to b5ab8c3 Compare June 26, 2026 09:45
@wiseelf wiseelf merged commit 32f8190 into main Jun 26, 2026
@wiseelf wiseelf deleted the feature/oncall-billing branch June 28, 2026 00:42
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