Skip to content

Opt Out of Coach Features — PRD

Product: Objectuve (codename: Enkidu) Feature: A master switch + per-surface toggles that let users turn Coach off — globally or surface-by-surface Status: Exploration / Design Date: April 2026 Owner: Josh Lockhart PRD file: docs/product/coach-opt-out-prd.md


1. Problem Statement

Coach is a first-class feature in Objectuve, but it is also an opinionated one — it pushes insight cards onto the dashboard, generates daily check-in prompts, proposes milestones when a goal is created, and sits behind a floating modal across six pages. For most users that's a feature; for a meaningful minority it's friction they can't remove.

Three concrete problems:

  1. No way to turn Coach off. A user who doesn't want AI-generated advice has no setting to disable it. They can dismiss individual insight cards (stored in localStorage) and ignore the modal, but check-in prompts keep arriving as ai_check_in notifications, milestone suggestions appear on every goal creation, and insight cards regenerate after cache expiry. Coach is always on.

  2. All-or-nothing would be wrong. The same user who finds daily Coach check-in prompts intrusive may still want milestone suggestions when creating a goal, or may want the modal available on demand but not the automated insight cards. A master switch alone is too blunt — the surfaces have different value densities and different opt-out rates.

  3. Brand-promise gap. Objectuve positions itself as an anti-social, respectful app ("10 min/day, then leave"). Coach today opts users in by default across every surface with no way out short of hiding the app. This is out of step with the rest of the product's consent posture (Go It Alone, Private Mode, per-goal privacy).

Opt Out of Coach Features fixes this with one master switch and a small set of per-surface toggles, storing preferences next to the existing coach_persona / coach_tone fields on user_detail.data, and gating every backend Coach path and every frontend Coach surface on those flags.


2. Goals & Success Metrics

Primary Goals

  1. Give every user a visible, trusted "Turn Coach off" setting that actually turns Coach off everywhere.
  2. Let users keep the Coach surfaces they value while turning off the ones they don't.
  3. Enforce opt-out in the backend — no Coach content should be generated for users who have opted out, not just hidden.
  4. Respect existing Coach configuration — turning Coach back on restores, not resets, the user's persona/tone/focus/frequency.

Success Metrics

MetricTarget (60 days post-launch)
% of MAU who open the Coach settings panel15%+
% of MAU who turn the master switch off3–8% (a real opt-out path exists; not the majority)
% of opt-out users who re-enable within 30 days<20% (signal the opt-out stuck)
% of users who use per-surface toggles (without master-off)5–10% (signal that granularity is actually used)
Reduction in Coach inference calls for opt-out users100% for master-off; ≥90% for per-surface-off
Regression in Coach engagement among non-opt-out users0 (guardrail)
Support tickets containing "turn off Coach" / "disable AI"↓ 80% within 60 days

Guardrails

  • No silent generation. If a surface is off, backend must not call an LLM for that user on that surface. This is a cost guardrail and a trust guardrail.
  • No cross-surface side effects. Turning off insight cards must not affect check-in prompts or modal access.
  • Defaults preserve today's behavior. All toggles default to on for existing users and new users — nobody's experience changes until they touch a toggle.
  • Master switch is absolute. When master is off, no Coach UI renders anywhere and no Coach job/mutation runs for that user, regardless of per-surface state.
  • Anti-social discipline. Settings panel is a single scrollable screen; no nested wizards.

3. User Stories

  • US-1: As a user who doesn't want AI in my goal tracking, I want a single "Turn off Coach" switch that reliably removes every Coach surface.
  • US-2: As a user who likes Coach but finds daily check-in prompts noisy, I want to keep Coach enabled but turn off just the check-ins.
  • US-3: As a user who only wants Coach on demand, I want to keep the Coach modal available but disable automatic insight cards and milestone suggestions.
  • US-4: As a user who turned Coach off, I want the next time I open the Coach modal or land on a Coach surface to show a respectful "Coach is off — turn it back on?" affordance, not a blank page.
  • US-5: As a user who configured Coach in Meet Coach and then turned it off, I want my persona/tone/focus/frequency preserved, so re-enabling restores my configuration exactly.
  • US-6: As a user, I want the Coach opt-out setting discoverable from both Settings and directly from any Coach surface (e.g. a small kebab menu on an insight card → "Turn off Coach check-ins").
  • US-7: As a user in an ai_check_in push notification flow, I want a one-tap way from that push to disable check-ins, so opt-out isn't buried in Settings.
  • US-8: As an admin looking at a user's profile, I want to see their Coach opt-out state so I can interpret low engagement correctly.

4. Feature Design

4a. The master switch

"Coach" — top of a new/expanded Coach section in Settings.

  • Label: Coach
  • Sub-label: "Personalized advice, insights, and check-ins built around your goals."
  • Toggle: on by default.

When off:

  • Every per-surface toggle below is visually disabled (grayed out) but retains its stored value.
  • The Coach modal entry point is removed from every page (Dashboard, Goals, Goal detail, Badges, Communities, Settings itself hides the modal trigger but keeps the settings panel).
  • All Coach mutations return { disabled: true, reason: 'coach_disabled_by_user' } without calling the LLM.
  • GenerateAiCheckInPromptsJob skips the user at the query stage.
  • ai_check_in push notifications are suppressed at Social::SendPushNotification.
  • Existing Coach history and preferences are preserved unchanged.

When toggled back on: all per-surface toggles become active again at their previously stored values.

4b. Per-surface toggles

Under the master switch, a stacked list of labeled rows. Each has: label, one-line description, toggle.

SurfaceToggle IDWhat turning this off does
Daily check-in promptscoach_check_ins_enabledGenerateAiCheckInPromptsJob skips this user. No ai_check_in notifications are created or pushed. CheckInPromptCard.vue never shows.
Insight cardscoach_insights_enabledGetInsight mutation returns disabled. useAiInsights.ts short-circuits and renders nothing. Dashboard / goal / goal-detail insight slots collapse cleanly.
Milestone suggestionscoach_milestone_suggestions_enabledGenerateMilestones mutation returns disabled. The "Suggest milestones with Coach" affordance in GoalCreate.vue / RoadmapBuilder.vue is hidden.
Goal description refinementcoach_description_refinement_enabledRefineDescription mutation returns disabled. The "Refine with Coach" button in the goal form is hidden.
On-demand advice (Coach modal)coach_modal_enabledGetAdvice mutation returns disabled. AiCoachModal.vue trigger is hidden from every page.

Default for all: on.

A small help link at the bottom of the section links to the Meet Coach page ("Prefer to change how Coach sounds instead? Tune your Coach →").

4c. In-context opt-out affordances

Settings should not be the only path. Each Coach surface exposes a quick opt-out:

  • Insight card → kebab menu now includes: "Dismiss once" (existing), "Turn off Coach insights" (new).
  • Check-in prompt card / push notification → overflow menu includes "Turn off Coach check-ins" (with a second-step confirmation toast: "Coach check-ins off. Change in Settings → Coach.").
  • Coach modal → footer adds a subtle "Turn off Coach" link that deep-links to Settings with the master switch focused.
  • Milestone suggestion inline → "Not useful? Turn off milestone suggestions."

Each in-context toggle writes the same preference the Settings panel writes. No parallel state.

4d. Settings panel placement & layout

New tab or section: Settings → Coach. It sits next to / between Privacy and Account. Structure:

Settings
├── Profile
├── Supporter
├── Coach   ←  new panel lives here
├── Privacy
├── Account
└── Feedback

Panel contents:

  1. Master switch (4a)
  2. Divider + heading: "What Coach does"
  3. Five per-surface toggles (4b) — stacked rows
  4. Divider + heading: "How Coach sounds"
  5. Link row: "Tune your Coach persona, tone, focus, and frequency →" (deep-link to /meet-coach)
  6. Footer: "Your preferences are preserved. Turn Coach off anytime without losing your setup."

4e. "Coach is off" states

When a user lands on a Coach-dependent entry point with Coach disabled:

  • Modal trigger taps when master is off: no-op (the trigger is hidden, so this shouldn't happen; but as a defensive fallback, a respectful toast: "Coach is off. Turn it on in Settings → Coach." with a "Settings" button).
  • Deep link to /meet-coach while master is off: renders Meet Coach normally (so the user can re-configure) with a banner: "Coach is currently off. Turn it back on to apply changes."
  • Insight/check-in surfaces: simply do not render. No "Coach is off" placeholder in those slots — that would be noisier than the surface it replaced.

4f. Relationship to existing features

FeatureInteraction
Meet CoachMeet Coach governs how Coach sounds (persona/tone/focus/frequency). Coach opt-out governs whether Coach runs at all. Orthogonal. A user can configure Coach in Meet Coach, then disable it; preferences are retained.
Go It Alone (Privacy PRD)Go It Alone already sets solo_mode in Coach prompts ("do not suggest community features"). It does not disable Coach. These are independent switches: a Go It Alone user may still want Coach; a Coach-off user may still want communities.
ai_check_in notificationsWhen check-ins are off (via master or per-surface), these notifications are not generated at all. This is stronger than Go It Alone's pause-at-delivery approach — we want no inference cost for opted-out users.
Coach historyNot touched. Past chat/modal history remains if we ever expose it. Out of scope for v1.

5. Data & API Changes

5a. Storage — extend user_detail.data JSONB

Coach preferences already live in user_detail.data (the existing JSONB bag that holds coach_persona, coach_tone, coach_focus_primary, coach_focus_secondary, coach_frequency, coach_configured_at). Add:

coach_enabled                          : boolean (default true)
coach_check_ins_enabled                : boolean (default true)
coach_insights_enabled                 : boolean (default true)
coach_milestone_suggestions_enabled    : boolean (default true)
coach_description_refinement_enabled   : boolean (default true)
coach_modal_enabled                    : boolean (default true)
coach_disabled_at                      : timestamp (set when master flips off, cleared on on)

No migration is required to the schema — the JSONB column already exists. The UpdateCoachingPreferences interaction and the accessor methods on User / UserDetail must be extended to read/write these keys with sensible defaults.

5b. User model helper

Add a predicate per surface plus a master helper:

ruby
# app/models/user.rb
def coach_enabled?
  coach_pref(:coach_enabled, default: true)
end

def coach_check_ins_enabled?
  coach_enabled? && coach_pref(:coach_check_ins_enabled, default: true)
end
# ... one per surface

Every backend Coach path calls the corresponding predicate before doing any work.

5c. GraphQL

  • CoachingPreferencesType (rails_api/app/graphql/types/coaching_preferences_type.rb) — add the 6 new boolean fields.
  • UpdateCoachingPreferences mutation — add the 6 new optional boolean arguments, plumbed through UserIdentity::UpdateCoachingPreferences.
  • All Coach mutations — each is extended to return early with { disabled: true, reason: 'coach_disabled_by_user' } (or a surface-specific reason) when the relevant predicate is false:
    • Mutations::Ai::GetAdvice — checks coach_modal_enabled?
    • Mutations::Ai::GetInsight — checks coach_insights_enabled?
    • Mutations::Ai::GenerateMilestones — checks coach_milestone_suggestions_enabled?
    • Mutations::Ai::RefineDescription — checks coach_description_refinement_enabled?

5d. Backend jobs

  • rails_api/app/jobs/ai_coaching/generate_ai_check_in_prompts_job.rb — scope User.all to User.where("(user_details.data->>'coach_enabled')::boolean IS NOT FALSE AND (user_details.data->>'coach_check_ins_enabled')::boolean IS NOT FALSE") (or equivalent via a User.coach_check_ins_enabled scope).
  • rails_api/app/interactions/social/send_push_notification.rb — early return for ai_check_in kind when user.coach_check_ins_enabled? is false. Belt-and-suspenders: the job shouldn't have created the notification in the first place, but push delivery should still guard.

5e. Frontend

  • ionic_frontend/src/constants/graphql/user.js (or the file that holds USER_QUERY) — extend the user fragment to include all 6 new coaching preference booleans.
  • ionic_frontend/src/composables/useAiCoach.ts — check user.coachModalEnabled before opening the modal; respect disabled response from GetAdvice.
  • ionic_frontend/src/composables/useAiInsights.ts — short-circuit the generation loop when user.coachInsightsEnabled is false; drop any cached insights for that user from localStorage on the flip.
  • ionic_frontend/src/components/dashboard/CheckInPromptCard.vue — conditionally render.
  • ionic_frontend/src/components/ui/AiInsightCard.vue + components/dashboard/streamlined/InsightCard.vue — conditionally render + surface the in-context "Turn off Coach insights" action (4c).
  • ionic_frontend/src/components/ui/AiCoachModal.vue — footer deep-link to Settings → Coach.
  • ionic_frontend/src/views/Settings.vue — new Coach panel (4d).
  • ionic_frontend/src/views/MeetCoach.vue — banner when Coach is off (4e).
  • ionic_frontend/src/views/GoalCreate.vue (and the RoadmapBuilder from the Goal Roadmap PRD) — conditionally render "Suggest with Coach" / "Refine with Coach" affordances.

6. Rollout Plan

  • No feature flag needed. The defaults preserve today's behavior for 100% of users — shipping the toggles doesn't ship a behavior change.
  • Instrumentation (UserAction triggers):
    • coach_disabled_master
    • coach_enabled_master
    • coach_surface_toggled (with surface: attribute)
    • coach_in_context_opt_out (with surface: attribute — fires when users use 4c in-context toggles)
  • Admin visibility: add a Coach section to the admin user detail view showing all 6 flags and the coach_disabled_at timestamp. Exists primarily so operators can interpret "this user has zero Coach events" correctly.
  • Docs:
    • Add this PRD to docs/product/prd-index.md under Exploration / Design.
    • Cross-link from docs/product/meet-coach-prd.md (add a one-line note that Coach opt-out is a separate, complementary surface).
    • Cross-link from docs/product/privacy-controls-prd.md (clarify that Go It Alone and Coach opt-out are independent).
    • When shipped, add a user-guide section.

7. Non-Goals (v1)

  • Per-goal Coach settings. Users can't turn Coach on for some goals and off for others. All-or-nothing within the account.
  • Coach history deletion. Turning Coach off preserves past content. A "delete my Coach history" control is a separate privacy PRD if ever needed.
  • Persona / tone editing. Already owned by Meet Coach.
  • Back-end inference budgeting per user. Cost guardrails beyond "don't run for opt-out users" belong in the dedicated AI service PRD.
  • A "light" Coach mode. If users find even one surface too much, they turn that surface off. No intermediate "reduced Coach" preset in v1.

8. Open Questions

  • Should the master switch also silence existing, unacknowledged ai_check_in notifications (mark as acknowledged) when flipped off, or leave them as history? Recommendation: leave them — Coach-off is forward-looking, not revisionist.
  • When a user flips master on after being off for a long time, should we trigger a one-time "Welcome back — here's what Coach can do" dashboard card? Recommendation: yes, a small GettingStartedCard-style reintroduction. Out of scope if it slips — fine to defer.
  • Should we show a one-time nudge after 14 days of never opening the Coach modal and zero interactions with insight cards, offering the opt-out? Worth testing, but deferred to a separate experiment PRD.

9. Key Files to Touch (implementation reference)

Backend

  • rails_api/app/models/user_detail.rb — new accessor methods on the JSONB bag
  • rails_api/app/models/user.rb — predicate helpers (coach_enabled?, coach_check_ins_enabled?, ...)
  • rails_api/app/interactions/user_identity/update_coaching_preferences.rb — accept + validate new boolean args
  • rails_api/app/graphql/types/coaching_preferences_type.rb — expose new fields
  • rails_api/app/graphql/mutations/update_coaching_preferences.rb — accept new args
  • rails_api/app/graphql/mutations/ai/get_advice.rb — early-disabled return
  • rails_api/app/graphql/mutations/ai/get_insight.rb — early-disabled return
  • rails_api/app/graphql/mutations/ai/generate_milestones.rb — early-disabled return
  • rails_api/app/graphql/mutations/ai/refine_description.rb — early-disabled return
  • rails_api/app/jobs/ai_coaching/generate_ai_check_in_prompts_job.rb — scope users
  • rails_api/app/interactions/social/send_push_notification.rbai_check_in guard
  • rails_api/spec/ — coverage on every predicate + disabled path

Frontend

  • ionic_frontend/src/views/Settings.vue — new Coach panel
  • ionic_frontend/src/views/MeetCoach.vue — "Coach is off" banner
  • ionic_frontend/src/views/GoalCreate.vue — gate milestone/refinement affordances
  • ionic_frontend/src/composables/useAiCoach.ts — gate modal + handle disabled response
  • ionic_frontend/src/composables/useAiInsights.ts — gate generation + cache cleanup
  • ionic_frontend/src/components/ui/AiCoachModal.vue — footer deep-link
  • ionic_frontend/src/components/ui/AiInsightCard.vue — in-context opt-out action
  • ionic_frontend/src/components/dashboard/CheckInPromptCard.vue — in-context opt-out action + conditional render
  • ionic_frontend/src/components/dashboard/streamlined/InsightCard.vue — conditional render
  • ionic_frontend/src/constants/graphql/user.js — extend user fragment
  • Storybook stories for the Settings Coach panel + "Coach is off" Meet Coach banner
  • Vitest coverage for composable gating; Cypress flow for toggle off → no insights → toggle on → insights return

Next step: review problem framing and surface inventory, then split into two implementation phases (backend predicates + mutation gating → frontend panel + in-context affordances).

Last updated: 2026-05-04

Loading…