A discipline app is most useful on the day you break the streak. Perfect weeks flatter the software. Missed days reveal whether the app has anything to do besides reset a counter.

PactBuddy is built around a narrower bet: discipline gets easier when one promise has proof, one trusted person can review it, and a miss has a next action already waiting. The app does not try to become your coach, therapist, or boss. It gives the bad day a path.

A discipline app needs a promise another person can recognize

Most discipline apps start with a private intention. Drink more water. Wake up earlier. Use the phone less. Those are fine as wishes, but they are weak as agreements because nobody can tell what happened.

PactBuddy asks for a promise that can survive being read by another person. The built-in goals handle wake-up, workout, and Screen Time. Custom goals use a small shape on purpose:

public struct CustomGoalConfig: Codable, Sendable, Hashable {
    public var title: String
    public var `operator`: Operator
    public var targetValue: Int
    public var unit: String
    public var cadence: String
    public var targetCount: Int
    public var deadlineLocal: String
}

That is the difference between "be disciplined" and "read 20 pages by 9 p.m." One lets you rewrite the promise after the day goes badly. The other gives your buddy something fair to recognize.

This is also why the broader accountability pact matters. Discipline is easier to talk about before the miss than after it.

A discipline app should make proof smaller than the promise

Proof does not need to be dramatic. It needs to answer the agreed question. PactBuddy keeps proof methods small and separate:

struct ProofSubmission: Sendable, Hashable {
    enum Method: String, Sendable, CaseIterable {
        case wakeSelfie = "wake_selfie"
        case photo
        case screentimeSync = "screentime_sync"
        case manual
    }
}

The check-in sheet chooses the proof path from the goal type:

if goal?.type == .screenTime {
    screenTimeProof
} else if goal?.type == .custom {
    manualProof
} else {
    photoProof
}

A wake-up goal gets a current selfie. A workout gets a photo. A Screen Time goal syncs a rounded result. A custom goal can be a manual check-in. That does not make the promise effortless. It makes the handoff clear enough that a real person can review it.

If the person on the other side is still vague, start with how to choose an accountability buddy. The role needs to be small before the app can help.

A discipline tracker app needs a missed-day state

A discipline tracker app that only counts good days trains you to hide from bad ones. The useful product question is what happens after the first miss.

PactBuddy models the miss as an incident. The owner and buddy see different actions from the same state:

enum IncidentViewerAction: Equatable {
    case submitProof
    case waitForOwnerProof
    case waitForBuddyReview
    case reviewProof
    case none
}

When the incident is open, the owner submits proof and the buddy waits. When proof arrives, the owner waits and the buddy reviews. That role split keeps the app from turning into a pile of reminders where both people wonder who is supposed to act.

The backend flow has the same shape. The owner submits proof. The buddy confirms it, rejects it, or uses a weekly vouch. A rejected check-in resumes paging instead of pretending the day is settled.

func submit(_ proof: ProofSubmission, backend: BackendClient) async -> Bool
func verify(_ incident: IncidentDTO, confirm: Bool, note: String?, backend: BackendClient) async -> Bool
func vouch(_ incident: IncidentDTO, backend: BackendClient) async -> Bool
func stopPager(_ incident: IncidentDTO, backend: BackendClient) async -> Bool

That is the part many streak apps skip. The miss is not a moral event. It is a state that needs a fair next step.

Pressure needs brakes or people stop using it

Discipline without brakes becomes nagging. PactBuddy puts limits on the reminder loop because the point is to return to the promise, not make the phone unbearable.

The app exposes the owner snooze in plain language:

Text("Pick a reason. Each missed goal can be snoozed up to three times, at least one hour apart.")

The model enforces the same cap on the client:

var snoozesRemaining: Int { max(0, 3 - snoozeCount) }

var nextSnoozeAvailableAt: Date? {
    lastSnoozedAt?.addingTimeInterval(60 * 60)
}

The local notification backstop also has a floor. PactBuddy uses Apple's UserNotifications framework and keeps the local cadence at 15 minutes or more:

content.interruptionLevel = incident.pageCount == 0 ? .timeSensitive : .active
let interval = max(900, TimeInterval(context.pact.pact.pageIntervalSec))

That is an opinion about discipline apps. A first missed-goal reminder can be urgent. Repeated pressure should be controlled, visible, and easy to stop for the day.

A self discipline app should protect the relationship

A self discipline app can accidentally make accountability feel like surveillance. That is especially true when proof touches private behavior: phone use, sleep, health, location, or photos.

PactBuddy keeps Screen Time proof narrow. The user sees the on-device report, but the buddy gets a rounded result:

Text("PactBuddy sends only a rounded bucket. Exact app usage never leaves your phone.")

The buddy review screen follows the same rule. It can show a proof photo or a Screen Time summary, then asks the buddy to make one decision:

Button("Confirm proof") { Task { await decide(true) } }
Button("Reject · resume paging") { Task { await decide(false) } }
Button("Use weekly vouch (\(store.snapshot.streak.vouchTokensRemaining) left)") { Task { await vouch() } }

That is enough power for the role. Your buddy can verify, reject, or excuse the miss. They do not need a live feed of your day to help you keep one promise.

For the narrow phone-use case, the Screen Time accountability app guide goes deeper on what should and should not leave the device.

What this discipline app does not solve

PactBuddy cannot want the goal for you. It cannot make a vague promise measurable after the fact. It cannot turn the wrong buddy into the right one.

It also does not try to manage every habit in your life. The app works best when the promise is small enough to verify and important enough that you are willing to let one person see the result.

That limit is the product. A discipline app should not become a private courtroom. It should help two people agree on the next honest action.

Start with one promise that keeps slipping

Pick the goal you keep renegotiating in your head. Write it so another person can tell whether it happened. Decide what proof is fair. Decide what a miss should trigger.

Then invite one person. If the next miss has a clear path, the app is doing useful work. If it does not, fix the promise before you add more reminders.