Agent skill
practice-gamification
Expert assistant for the daily practice and gamification system in Raamattu Nyt. Covers the shared practice package (packages/shared-practices/), scheduling, streaks, session tracking, completion flow, stats, grand plans, discipleship mode, task reordering, and future rewards/badges. Use when (1) adding or modifying practice features (scheduling, completion, timers), (2) working on streaks, stats, or gamification (badges, rewards, XP), (3) implementing PracticeCompletionScreen or post-completion flows, (4) creating new practice types or content integrations (prayer sets, spiritual paths, calendars), (5) building admin practice management pages, (6) debugging practice session flow or streak calculation issues, (7) working on encouragement messages shown after practice completion, (8) working on grand plans (personal/curated task collections, sort order, auto-linking), (9) working on discipleship mode / cinema task orchestration, (10) working on task reordering on the Tänään page, (11) working on reading duration estimates, (12) working on the "Valitse tehtävä" subscribe section (reading plan picker + practice activation), (13) working on TodayTaskList empty state or useTodayDashboard unified hook. Trigger keywords: practice, streak, gamification, harjoitus, schedule, completion, timer, badge, reward, practice template, prayer set, spiritual path, encouragement, rohkaisu, completion screen, grand plan, discipleship, tänään, task order, reorder, järjestys, cinema tasks, lukuaika, reading duration, valitse tehtävä, FeaturedReadingPlanPicker, useTodayDashboard, empty state.
Install this agent skill to your Project
npx add-skill https://github.com/Spectaculous-Code/raamattu-nyt/tree/main/.claude/skills/practice-gamification
SKILL.md
Practice & Gamification System
Architecture
packages/shared-practices/ Shared monorepo package
├── src/
│ ├── index.ts Public API (hooks, components, utils)
│ ├── types.ts All TypeScript interfaces (incl. Grand Plan types)
│ ├── hooks/ React Query hooks (Supabase RPCs)
│ │ ├── useGrandPlan.ts Grand plan CRUD + reorder
│ │ ├── useTodayTasks.ts Unified task builder (practices + plans + prayers)
│ │ └── ... Practice hooks
│ ├── components/ Shared UI components
│ └── utils/practiceIcons.ts Icon mapping (PRACTICE_ICON_MAP)
│
DB: practice schema 10+ tables, 20+ RPCs (incl. grand_plan tables)
Apps: apps/raamattu-nyt/ Primary consumer (TanaanPage, CinemaReader, 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_streaksRPC - 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 |
useGrandPlan |
Grand plan CRUD + reorder | Multiple RPCs (see Grand Plan) |
useTodayTasks |
Unified task builder | — (pure computation) |
useDiscipleshipTasks |
All tasks for discipleship cinema | Composes multiple hooks |
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 |
FeaturedReadingPlanPicker |
apps/raamattu-nyt/src/components/today/ |
Top 3 reading plans with join/active badges for "Valitse tehtävä" section |
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
- Insert into
practice.practice_templates(migration) - Add icon to
PRACTICE_ICON_MAPinutils/practiceIcons.ts - 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
- Extract hook/component from
apps/raamattu-nyt/ - Place in
packages/shared-practices/src/ - Export from
index.ts - Update app imports to
@shared-practices/... - Avoid app-specific imports (use generic Supabase client pattern)
Grand Plan System
Orchestration layer that groups diverse content types into named task collections with sort order.
Concepts
- Personal plan: auto-created per user, holds their active practices/plans/prayers
- Curated plan: admin-created, shared across users (e.g. "30 Days of Prayer")
- Auto-linking: triggers automatically add/remove items when practices, reading plans, or prayers are activated/deactivated
- Sort order:
grand_plan_items.sort_orderdefines task display order on Tänään page
DB Tables (practice schema)
| Table | Purpose |
|---|---|
grand_plans |
Plan metadata: name, type (personal/curated), progression_type, created_by |
user_grand_plans |
Membership: user_id, is_primary, current_day, status |
grand_plan_items |
Tasks: item_type, item_ref (UUID), template_ref, sort_order, day_number |
Item types: practice, reading_plan, prayer, prayer_calendar, kooste
RPCs
| RPC | Purpose |
|---|---|
get_or_create_personal_plan(user_id) |
Returns/creates personal plan UUID |
add_item_to_grand_plan(...) |
Add item with auto sort_order |
remove_item_from_grand_plan(plan_id, item_id) |
Remove item |
reorder_grand_plan_items(plan_id, item_ids[]) |
Batch reorder by array position |
get_user_grand_plans() |
List user's plans with item_count |
get_grand_plan_items(plan_id) |
Items with resolved_name/icon |
join_grand_plan(plan_id) |
Join curated plan |
Auto-Link Triggers
| Trigger | Source table | Action |
|---|---|---|
trg_practice_auto_link_grand_plan |
practice.practices INSERT |
Add item_type='practice' |
trg_practice_auto_unlink_grand_plan |
practice.practices UPDATE (deactivation) |
Delete item |
trg_reading_plan_auto_link_grand_plan |
bible_schema.user_reading_plans INSERT |
Add item_type='reading_plan' |
trg_reading_plan_auto_unlink_grand_plan |
bible_schema.user_reading_plans UPDATE |
Delete item |
trg_prayer_auto_link_grand_plan |
public.prayers INSERT (status='active') |
Add item_type='prayer' |
trg_prayer_auto_unlink_grand_plan |
public.prayers UPDATE (deactivated) |
Delete item |
useGrandPlan Hook
const { plan, items, reorderItems, addItem, removeItem, loading } = useGrandPlan();
// reorderItems(itemIds[]) — optimistic UI + invalidates cache
Admin
AdminGrandPlansTab in AdminPracticesPage — CRUD for plans and items, accessed via admin RPCs.
Task Reordering (Tänään Page)
Users reorder tasks via useTodayDashboard().reorderItems.
Flow
TanaanPage
├── useTodayDashboard() → { items, reorderItems }
│ items already sorted by sort_order from the RPC
└── Pass onReorder to TodayTaskList
TodayTaskList
├── "Muokkaa järjestystä" toggle → shows ChevronUp/Down per row
└── handleSwap → collect item_ids in new order → onReorder(itemIds)
└── reorderItems(itemIds) → reorder_grand_plan_items RPC + cache invalidation
Discipleship Mode
Full-screen Cinema Reader orchestration for structured daily routines.
Entry Point
TanaanPage → two cinema buttons → CinemaReaderScreen with discipleshipTasks
Two Launch Modes
| Mode | Button | Props | Behavior |
|---|---|---|---|
| Reading Plan Cinema | Blue Play | discipleshipTasks={readingPlanCinemaTasks} autoStart |
Auto-plays reading plans only. No task selector, no quizzes, no transitions. |
| Discipleship Cinema | Amber Clapperboard | discipleshipTasks={allTasks} |
Full flow: task selector → reading + prayer + practice → quizzes → kooste. |
Hook: useDiscipleshipTasks
Location: apps/raamattu-nyt/src/hooks/useDiscipleshipTasks.ts
Composes multiple data sources into UnifiedTodayTask[]:
- Active practices (filtered: no prayer_set, no empty prayer_calendar)
- Active reading plans (with duration from
useReadingPlanDurations, passesprogression_type) - Today's scheduled prayers
- Appends "Pohdittavat jakeet" (kooste) task at the end (
NYT_KOOSTE_TASK_ID)
useTodayTasks (Unified Task Builder)
Location: packages/shared-practices/src/hooks/useTodayTasks.ts
Normalizes heterogeneous sources into UnifiedTodayTask[]:
- ID format:
rp-{id},pr-{id},py-{id} - Duration: reading plans use
estimateReadingMinutes(verseCount), practices use template default, prayers use fallback ReadingPlanInputaccepts optionalprogression_typefor correct completion detection- isCompletedToday for reading plans: Checks
completed_days.includes(current_day)OR for completion-type plans (not calendar/day_of_year) also checkscompleted_days.includes(current_day - 1)becausemark_reading_day_completeadvancescurrent_dayimmediately - Returns:
{ tasks, completed, uncompleted, totalDurationMinutes }
CinemaReaderScreen Discipleship Flow
Open with discipleshipTasks + optional autoStart/initialTask
│
├─► autoStart=true → queue all uncompleted, start first
│ No selector, no toggle, no quizzes, no transitions
│
├─► initialTask provided → start immediately
│
└─► No initialTask → show DiscipleshipTaskSelector
│
▼
Task execution:
├─ reading_plan → fetch verses, display in cinema
│ → mark_reading_day_complete RPC on finish
│ → auto-insert memory quiz after (discipleship only, NOT autoStart)
│ → quiz prioritizes user-marked verses (marked_refs from VerseBar)
├─ practice/prayer → DiscipleshipInlineTask (inline view)
└─ kooste → load accumulated verse refs, display in cinema
│
▼
Task complete → DiscipleshipTransitionOverlay (auto-skipped in autoStart)
├─ Show completion summary
├─ "Next task" / free task selection
└─ Skip completed tasks in queue
Reading Plan Completion Gotcha
mark_reading_day_complete advances current_day immediately (5→6). This affects:
- Dashboard (
get_today_dashboard): For completion-type plans, checkscompleted_at::date = CURRENT_DATE(notcurrent_day) - Client (
useTodayTasks): Also checkscompleted_days.includes(current_day - 1)for completion-type plans - Query invalidation: Must invalidate
["today-dashboard"],["reading-plan-streaks"], AND["user-reading-plans"]
Quiz Marked Verse Priority
When user marks verses via DiscipleshipVerseBar (add to Nyt Kooste), those refs are passed as marked_refs in the quiz task metadata. useReadingPlanQuiz prioritizes them:
- 2+ marked in plan → both quiz verses from marked
- 1 marked → first from marked, second from remaining
- 0 marked → prefer NT/Psalms/Proverbs
Reading Duration Estimates
Location: apps/raamattu-nyt/src/lib/readingDuration.ts
countVersesFromReadings(readings)— total verses across references (handles multi-chapter ranges)estimateReadingMinutes(verseCount)—Math.max(1, Math.round(verseCount * 0.13))(~8 sec/verse)- Used by
useReadingPlanDurationshook to compute per-plan estimated minutes
Tänään Page Integration
The Tänään (Today) page (TanaanPage.tsx) is the primary consumer of practice data.
Architecture
TanaanPage
├── useTodayDashboard() → unified dashboard (replaces ~13 hooks)
│ └── get_today_dashboard RPC → DashboardItem[] (practices, plans, prayers, kooste)
├── useDiscipleshipTasks() → tasks for Cinema discipleship mode
├── Daily tasks section (#today)
│ └── TodayTaskList
│ ├── Empty state: "Ei tehtäviä" → scrolls to #subscribe
│ ├── Uncompleted tasks ("Mahdollisuudet")
│ ├── "Pohdittavat jakeet" (kooste) row
│ └── Completed tasks ("Tehty tänään")
│ ├── Blue Play button → Reading Plan Cinema (autoStart)
│ └── Amber Clapperboard → Discipleship Cinema
├── Tomorrow preview (#tomorrow)
│ └── TomorrowTasksSection
├── Permanent prayers (#permanent)
│ └── PermanentTasksSection (priority_level='always')
├── Recurring tasks (#recurring)
│ └── RecurringTasksSection
└── "Valitse tehtävä" section (#subscribe)
├── Always visible (not gated on templates.length)
├── Auto-opens when user has zero dashboard items
├── FeaturedReadingPlanPicker (top 3 global plans, "Aloita"/"Aktiivinen")
└── Harjoitukset (PracticeActivationCard per template)
Unified Dashboard Hook: useTodayDashboard
Location: apps/raamattu-nyt/src/hooks/useTodayDashboard.ts
Replaces the old pattern of calling useGrandPlan + useTodayPractices + useReadingPlans separately.
Single RPC get_today_dashboard(p_grand_plan_id) returns DashboardItem[] with:
item_type: practice | reading_plan | prayer | kooste | mini_taskis_completed_today,current_streak,sort_ordermetadataobject varies by item_type (seeDashboardPracticeMetadata, etc.)
Provides converter functions: toPracticeItem(), toReadingPlan(), toPrayer().
"Valitse tehtävä" Section
Renamed from "Valitse harjoitus". Contains two sub-sections:
-
Lukusuunnitelmat —
FeaturedReadingPlanPickercomponent- Shows top 3 available plans from
useReadingPlans().availablePlans - "Aloita" button calls
joinPlanWithCheck, shows "Aktiivinen" badge if joined - Waits for
userPlansLoadingbefore rendering to avoid false "Aloita" state - "Näytä kaikki" link →
/reading-plans onPlanJoinedcallback invalidates dashboard
- Shows top 3 available plans from
-
Harjoitukset — existing
PracticeActivationCardlist (unchanged)
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:
const { data } = useTodayPractices(); // today (no date param)
const { data } = useTomorrowPractices(); // calls get_practice_items_for_date with tomorrow
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.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
docs-updater
Expert assistant for keeping documentation synchronized with code changes in the KR92 Bible Voice project. Use when updating API docs, maintaining architecture diagrams, syncing README, updating CLAUDE.MD, or generating documentation from code.
ai-prompt-manager
Expert assistant for managing AI prompts, features, and configuration in the KR92 Bible Voice AI system. Use when creating AI prompts, configuring AI features, managing prompt versions, setting up AI bindings, or working with AI pricing and models.
performance-auditor
Expert assistant for monitoring and optimizing performance in the KR92 Bible Voice project. Use when analyzing query performance, optimizing database indexes, reviewing React Query caching, monitoring AI call costs, or identifying N+1 queries.
edge-function-generator
Expert assistant for creating and maintaining Supabase Edge Functions for the KR92 Bible Voice project. Use when creating Edge Functions, setting up CORS, integrating shared modules, adding JWT validation, or configuring environment variables.
admin-panel-builder
Expert assistant for creating and maintaining admin panel pages in the KR92 Bible Voice project. Use when creating admin pages, building admin components, integrating with admin navigation, or adding admin features.
lint-fixer
Expert assistant for analyzing and fixing linting and formatting issues in the KR92 Bible Voice project using Biome and TypeScript. Use when fixing lint errors, resolving TypeScript issues, applying code formatting, or reviewing code quality.
Didn't find tool you were looking for?