Files
Sauci/docs/content-balance-implementation.md

6.2 KiB

Content Balance Implementation

Transform Sauci from binary explicit/clean filtering to graduated intensity-based control.

Overview

Problem

The current binary "Spicy Content" toggle is too coarse. Users either see ALL explicit content or NONE, which:

  • Makes the app appear like "smut" to App Store reviewers if explicit is on
  • Provides no middle ground for users who want flirty but not explicit content

Solution

Replace the binary toggle with a 5-level intensity slider:

Level Label Content Type
1 Light Conversations, compliments, emotional connection
2 Mild Cuddling, gentle kisses, flirty texts
3 Moderate Making out, massage, suggestive content
4 Spicy Intimate activities, explicit content
5 Intense Advanced intimacy, kinks, fantasies

Key Changes

  • Default: New users see max intensity 2 (Mild)
  • Control: Graduated 1-5 slider instead of on/off
  • Filtering: Filter by question intensity, not just pack explicit flag
  • Display: Show intensity range on pack cards

Tasks

Phase 1: Database Migration

File: apps/supabase/migrations/YYYYMMDDHHMMSS_add_intensity_levels.sql

  • Add max_intensity column to profiles table (INTEGER, default 2, CHECK 1-5)
  • Migrate existing users: show_explicit_content=true → 5, else → 2
  • Add min_intensity, max_intensity, avg_intensity columns to question_packs
  • Create trigger to auto-update pack intensity stats when questions change
  • Backfill intensity stats for existing packs
-- Example migration
ALTER TABLE profiles
ADD COLUMN max_intensity INTEGER DEFAULT 2
CHECK (max_intensity >= 1 AND max_intensity <= 5);

UPDATE profiles SET max_intensity = CASE
  WHEN show_explicit_content = true THEN 5
  ELSE 2
END;

Phase 2: Mobile Types

File: apps/mobile/src/types/index.ts

  • Add max_intensity: 1 | 2 | 3 | 4 | 5 to Profile interface
  • Add min_intensity?: number, max_intensity?: number, avg_intensity?: number to QuestionPack interface

Phase 3: IntensitySlider Component

File: apps/mobile/src/components/ui/IntensitySlider.tsx (NEW)

  • Create slider component with 5 levels
  • Add flame icons that fill based on selected level
  • Display label and description for current level
  • Apply glass-morphism styling matching app theme
  • Add haptic feedback on level change
interface IntensitySliderProps {
  value: 1 | 2 | 3 | 4 | 5;
  onValueChange: (value: 1 | 2 | 3 | 4 | 5) => void;
  disabled?: boolean;
}

Phase 4: Onboarding Update

File: apps/mobile/app/(app)/onboarding.tsx

  • Replace Stage 4 binary choice with IntensitySlider
  • Update heading: "Set Your Comfort Level"
  • Change default state from showExplicit: false to maxIntensity: 2
  • Update profile save to set both fields for backwards compatibility

Current Stage 4:

"Show me everything" vs "Keep it clean"

New Stage 4:

"Set Your Comfort Level"
[Slider: 1 ----*---- 5]
         Light     Intense

Current: "Mild - Cuddling, gentle kisses, flirty texts"

Note: "Start gentle - you can turn up the heat anytime"

Phase 5: Settings Update

File: apps/mobile/src/features/profile/components/PrivacySettings.tsx

  • Replace SwitchItem with IntensitySlider

File: apps/mobile/src/features/profile/hooks/useProfileSettings.ts

  • Change state from showExplicit: boolean to maxIntensity: number
  • Update handler to save both max_intensity and show_explicit_content
  • Call fetchPacks() after intensity change to refresh filtered packs

Phase 6: Pack Store Filtering

File: apps/mobile/src/store/packsStore.ts

  • Update fetchPacks() to filter by intensity instead of explicit flag
// Current
if (!showExplicit) {
  query = query.eq("is_explicit", false);
}

// New
const maxIntensity = useAuthStore.getState().user?.max_intensity ?? 2;
query = query.or(`max_intensity.is.null,max_intensity.lte.${maxIntensity}`);

Phase 7: Pack Display Enhancement

File: apps/mobile/app/(app)/packs/questions.tsx

  • Add intensity badge to pack cards showing flame icons
  • Display intensity label: "Light" or "Light - Moderate" range

Phase 8: Default Packs for New Couples

File: apps/supabase/functions/manage-couple/index.ts

  • When couple forms, auto-enable packs where avg_intensity <= 2
const { data: safePacks } = await supabase
  .from('question_packs')
  .select('id')
  .lte('avg_intensity', 2)
  .eq('is_public', true);

await supabase.from('couple_packs').insert(
  safePacks.map(p => ({ couple_id, pack_id: p.id, enabled: true }))
);

Files Summary

File Action
apps/supabase/migrations/XXX_add_intensity_levels.sql CREATE
apps/mobile/src/types/index.ts MODIFY
apps/mobile/src/components/ui/IntensitySlider.tsx CREATE
apps/mobile/app/(app)/onboarding.tsx MODIFY
apps/mobile/src/features/profile/components/PrivacySettings.tsx MODIFY
apps/mobile/src/features/profile/hooks/useProfileSettings.ts MODIFY
apps/mobile/src/store/packsStore.ts MODIFY
apps/mobile/app/(app)/packs/questions.tsx MODIFY
apps/supabase/functions/manage-couple/index.ts MODIFY

Backwards Compatibility

  1. Keep show_explicit_content column during transition period
  2. Sync both fields when user changes intensity:
    • max_intensity >= 3 → show_explicit_content = true
    • max_intensity < 3 → show_explicit_content = false
  3. Deprecation: Remove show_explicit_content after 2-3 app update cycles

App Store Benefits

After implementation:

  • Default experience shows only Light/Mild content (levels 1-2)
  • App Store screenshots will feature connection-focused questions
  • Users must consciously opt-in to see spicier content
  • Categories already ordered with connection-focused content first