Agent Skills: Practice & Gamification System

>

UncategorizedID: Spectaculous-Code/raamattu-nyt/practice-gamification

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Spectaculous-Code/raamattu-nyt/tree/HEAD/.claude/skills/practice-gamification

Skill Files

Browse the full folder contents for practice-gamification.

Download Skill

Loading file tree…

.claude/skills/practice-gamification/SKILL.md

Skill Metadata

Name
practice-gamification
Description
>

Practice & Gamification System

Architecture

packages/shared-practices/     Shared monorepo package
├── src/
│   ├── index.ts               Public API (11 hooks, 4 components, 1 util)
│   ├── types.ts               All TypeScript interfaces
│   ├── hooks/                  React Query hooks (Supabase RPCs)
│   ├── components/             Shared UI components
│   └── utils/practiceIcons.ts  Icon mapping (PRACTICE_ICON_MAP)
│
DB: practice schema             7+ tables, 14+ RPCs
Apps: apps/raamattu-nyt/        Primary consumer (PrayerPage, admin)

Key Concepts

Schedule Types

| Type | Behavior | |------|----------| | daily | Every day (default) | | weekly | Specific weekdays [1,3,5] = Mon/Wed/Fri | | every_n_days | Every N days | | rotating_list | Cycle through list items | | spontaneous | No schedule, manual only |

Content Types (Polymorphic)

| Type | Ref points to | |------|---------------| | prayer_set | prayer_sets.id | | spiritual_path | spiritual_paths.id | | prayer | prayers.id | | prayer_calendar | prayer_calendars.id |

Session Flow

activate_practice_template(template, schedule, content)
        │
        ▼
get_today_practice_items()  →  TodayPracticeItem[]
        │
        ├─► Quick complete: start + complete in one roundtrip
        │
        └─► Timed session:
            start_practice_session(practice_id)
                │  usePracticeSession (client timer)
                ▼
            complete_practice_session(session_id, notes, metrics)
                │
                ▼
            PracticeCompletionScreen (streak, rhythm, verse)

Streaks

  • DB-computed via get_practice_streaks RPC
  • Schedule-aware: weekly practices don't break on off-days
  • Returns: current_streak, longest_streak, total_completions

Guest Support

All RPCs accept p_guest_session_id. Unauthenticated users work via useGuestSession().

Hooks

| Hook | Purpose | Key RPC | |------|---------|---------| | useTodayPractices | Today's items + start/complete/quickComplete | get_today_practice_items | | usePracticeTemplates(systemId) | Available templates | get_practice_templates | | useActivatePractice | Activate with schedule + content | activate_practice_template | | useDeactivatePractice | Remove practice | deactivate_practice | | usePracticeSession | Client timer (no DB) | — | | usePracticeHistory(practiceId?) | Paginated history | get_practice_history | | usePracticeStreaks | Streaks per practice | get_practice_streaks | | usePracticeStats(practiceId?) | Monthly/yearly stats | get_practice_stats | | useUpdateSchedule | Change schedule | update_practice_schedule | | useWeeklyRhythm(practiceId) | Week completed/scheduled | get_weekly_practice_rhythm | | usePrayerSets(systemId) | Prayer set options | get_prayer_sets | | useSpiritualPaths(systemId) | Spiritual path options | get_spiritual_paths | | useUserPrayers | User's prayers | Direct query | | useUserPrayerCalendars | User's calendars | Direct query |

Components

| Component | Purpose | |-----------|---------| | PracticeTaskRow | Practice row: icon, name, streak badge, quick-complete button, expandable verse | | PracticeTimer | Running timer with pause/resume | | PracticeScheduleEditor | Schedule type picker + weekday/interval config | | PracticeCompletionScreen | Post-completion: checkmark, duration, streak, weekly rhythm bar, encouragement, verse |

Encouragement System

Post-completion encouragement messages shown on PracticeCompletionScreen.

DB Table: public.encouragement_messages

| Column | Type | Notes | |--------|------|-------| | id | uuid | PK | | system_id | text | 'raamattu-nyt' default | | text_fi | text | Finnish message | | text_en | text | English (optional) | | practice_type | text | null = generic, or specific type filter | | is_active | bool | | | sort_order | int | |

Service: apps/raamattu-nyt/src/lib/encouragementService.ts

  • getRandomEncouragement(systemId, practiceType?, lang) — random active message
  • Admin CRUD: getAllEncouragements, createEncouragement, updateEncouragement, deleteEncouragement, toggleEncouragementActive

Hook: apps/raamattu-nyt/src/hooks/useEncouragementMessage.ts

  • useEncouragementMessage(practiceType?, osisRef?){ message, verseText, verseReference, loading }
  • Verse fallback chain: 1) practice osis_ref, 2) encouragement reading plan verse, 3) daily slogan
  • Encouragement plan configured via app_config.encouragement_reading_plan_id

Admin: AdminPracticesPage.tsx tabs

| Tab | Component | Purpose | |-----|-----------|---------| | Harjoitukset | inline | Template CRUD | | Tilastot | inline | Aggregate stats | | Rukoussetit | AdminPrayerSetsTab | Prayer set CRUD + items | | Hengelliset polut | AdminSpiritualPathsTab | Spiritual path CRUD | | Rohkaisut | AdminEncouragementTab | Encouragement message CRUD | | Rohkaisujae | AdminEncouragementVerseTab | Reading plan verse config |

App-Side Components (not in shared-practices)

| Component | Location | Purpose | |-----------|----------|---------| | PracticeSessionDialog | apps/raamattu-nyt/src/components/practice/ | Full session dialog: content card, timer, notes, completion screen | | PracticeActivationCard | same | Template activation with schedule + content picker | | PracticeHistory | apps/raamattu-nyt/src/components/profile/ | Profile section history list | | PracticeStatsCard | same | Profile section stats card | | ProfilePractices | same | Profile practices section | | AdminPracticesPage | apps/raamattu-nyt/src/pages/ | Full admin page with 6 tabs |

Completion Flow (PracticeSessionDialog)

User clicks practice row
    │
    ├─► Quick complete (check button): quickComplete(practiceId)
    │   └─► No dialog, just toggles done
    │
    └─► Timed session (row click): opens PracticeSessionDialog
        │
        ├─ Shows content card (prayer set item / path step / custom)
        ├─ Shows verse text if osis_ref present
        ├─ PracticeTimer with pause/resume
        ├─ Notes textarea
        │
        └─► Complete button:
            completeSession(sessionId, notes, metrics)
                │
                ▼
            PracticeCompletionScreen
            ├─ Duration + streak stats
            ├─ Weekly rhythm bar (X/Y)
            ├─ Encouragement message (random from DB)
            └─ Verse card (osis_ref or encouragement plan)

Implementation Patterns

Adding a New Practice Type

  1. Insert into practice.practice_templates (migration)
  2. Add icon to PRACTICE_ICON_MAP in utils/practiceIcons.ts
  3. If content-linked: add content_type handler in activation flow

Dual-Write Pattern

Prayer practices write to both practice_sessions AND prayer_logs for backward compat. The complete_practice_session RPC handles this internally.

Query Key Conventions

["today-practices", stableId]
["practice-streaks", stableId]
["practice-stats", stableId, practiceId]
["practice-history", stableId, practiceId]
["practice-templates", systemId]
["weekly-practice-rhythm", practiceId, stableId]

Moving Features to shared-practices

  1. Extract hook/component from apps/raamattu-nyt/
  2. Place in packages/shared-practices/src/
  3. Export from index.ts
  4. Update app imports to @shared-practices/...
  5. Avoid app-specific imports (use generic Supabase client pattern)

Tänään Page Integration

The Tänään (Today) page (TanaanPage.tsx) is the primary consumer of practice data.

Architecture

TanaanPage
├── Daily tasks section (content_type !== "prayer_calendar")
│   └── PracticeTaskRow[] from useTodayPractices()
├── Recurring tasks section (prayer_calendar + prayer_set items)
│   └── RecurringTasksSection (calendarPracticeItems)
└── Tomorrow preview
    └── TomorrowTasksSection (filters OUT prayer_calendar items)

prayer_calendar Content Type Gotcha

Prayer calendar items always resolve the latest active prayer from the calendar, not a today-scheduled prayer. The RPC fetches prayers where calendar_id = content_ref and status = 'active', ordered by most recent.

get_practice_items_for_date RPC

Parameterized version of get_today_practice_items that accepts a target date:

// Today's items (no date param)
const { data } = useTodayPractices();

// Tomorrow's items (with date param)
const { data } = useTomorrowPractices(); // calls get_practice_items_for_date with tomorrow

This RPC handles all content types (prayer_calendar, prayer_set, spiritual_path, prayer) and correctly computes schedule-based occurrence dates.

Future: Rewards & Badges (Not Yet Implemented)

Design considerations:

  • Streak milestones (7, 30, 100 days) trigger rewards
  • Badges for completing spiritual paths
  • XP from session metrics (duration, verse count)
  • Tables: practice.user_rewards, practice.user_badges
  • PracticeCompletionScreen already shows streak — extend with badge unlock

DB Reference

See references/db-schema.md for full table and RPC reference.