Mood Logging — Feature & Testing Guide
Overview
Mood logging allows users to record how they're feeling with a quick daily check-in. Each mood log captures one of six mood states, an optional text note (max 300 characters), and an optional link to a specific goal. Mood data helps users track emotional patterns alongside their goal progress.
Mood Check-In Flow
Opening the Mood Check-In
The mood check-in modal can be accessed from:
- Dashboard — In the "Today's practice" section, when no mood has been logged yet, a "How's today feeling?" prompt appears as a tappable row. Tapping it opens the mood check-in modal in goal-less mode, allowing users to log a general daily mood. Once a mood is logged, the prompt is replaced with a confirmation row that reads "Mood logged today."
- Goal Detail Page — when logging an update, a mood selector is embedded in the GoalEventForm
Step-by-Step Flow
- Modal opens with "How are you feeling?" header
- Select a mood from the Mood Selector (required):
| Mood | Emoji | Value |
|---|---|---|
| Amazing | 😄 | amazing |
| Happy | 😊 | happy |
| Calm | 😌 | calm |
| Meh | 😐 | meh |
| Tired | 😴 | tired |
| Low | 😞 | low |
- Optionally link to a goal via the Goal Selector dropdown
- Shows "No goal" as the first option (null value)
- Lists all active goals (label/value pairs)
- Optionally add a Note (max 300 characters)
- Auto-grow textarea with "What's on your mind?" placeholder
- Character counter shows
X/300at bottom
- Tap Log Check-In to submit
What Gets Stored
mood: Required enum — one ofamazing,happy,calm,meh,tired,lownote: Optional string, max 300 characters (trimmed before submission)goal_id: Optional FK linking the mood to a specific goaluser_id: Auto-set from authenticated usercreated_at: Timestamp of the check-in
Key Behaviors
- Mood is required — the submit button is disabled until a mood is selected
- Note and goal are optional — only included in the mutation if provided
- The form resets after successful submission
- The modal emits a
closedevent and calls theonSuccesscallback on submit
Mood Selector Component
The MoodSelector is a reusable component used in both MoodCheckIn and GoalEventForm:
- Displays 6 mood options in a horizontal row
- Each option is a circular button with emoji + label
- Selected mood gets a highlight ring and slightly enlarged styling
- Emits the selected mood value to the parent component
- Visual feedback on tap/click
Where Mood Data Appears
- Goal Events — When a mood is selected during a goal update, it's attached to the
GoalEventrecord and displayed as a mood emoji badge on the timeline event card - User Profile / Stats — Mood history contributes to wellness tracking metrics
- Coach Insights — The Coach may reference mood patterns when generating insights
UI Components
MoodCheckIn Modal
- Header: "How are you feeling?" with close button (X)
- Body: MoodSelector + Goal dropdown + Note textarea with counter
- Footer: "Log Check-In" button (disabled until mood selected)
- Loading state: button shows spinner during submission
MoodSelector
- Horizontal flex row of 6 emoji buttons
- Each button: emoji icon + mood label text
- Selected state: primary-colored ring, subtle scale transform
- Accessible: buttons have aria labels
Testing the Feature
Manual Testing Checklist
1. Open Mood Check-In
- [ ] Navigate to Dashboard
- [ ] Open the mood check-in modal
- [ ] Verify "How are you feeling?" header displays
- [ ] Verify 6 mood options are shown
2. Select a Mood
- [ ] Tap "Amazing" → verify it highlights with selection ring
- [ ] Tap "Meh" → verify Amazing deselects and Meh highlights
- [ ] Verify only one mood can be selected at a time
- [ ] Verify "Log Check-In" button becomes enabled after selection
3. Submit Without Optional Fields
- [ ] Select a mood
- [ ] Leave note empty and goal unselected
- [ ] Tap Log Check-In
- [ ] Verify submission succeeds
- [ ] Verify modal closes
4. Submit With Note
- [ ] Select a mood
- [ ] Type a note: "Feeling good about my progress"
- [ ] Verify character counter shows correct count (e.g., "35/300")
- [ ] Submit → verify success
5. Note Character Limit
- [ ] Type a note approaching 300 characters
- [ ] Verify counter updates in real-time
- [ ] Verify input stops at exactly 300 characters (maxlength enforced)
- [ ] Verify counter shows "300/300"
6. Link to a Goal
- [ ] Select a mood
- [ ] Open the goal dropdown
- [ ] Verify "No goal" is the first option
- [ ] Select a goal from the list
- [ ] Submit → verify the mood log is linked to the goal
7. Submit with All Fields
- [ ] Select "Happy" mood
- [ ] Link to a goal
- [ ] Add a note: "Great session today"
- [ ] Submit → verify all fields are saved
- [ ] Verify the form resets after submission
8. Validation
- [ ] Open the modal without selecting a mood
- [ ] Verify "Log Check-In" button is disabled
- [ ] Verify button text is not "Saving..."
- [ ] Select a mood → verify button enables
9. Mood in Goal Events
- [ ] Navigate to a goal detail page
- [ ] Tap Add Update
- [ ] Verify MoodSelector appears in the event form
- [ ] Select a mood, add content, submit
- [ ] Verify the mood emoji appears on the event card in the timeline
Smoke Tests
Automated smoke test:
ionic_frontend/tests/smoke-playwright/specs/mood-lifecycle/06-mood-log.spec.ts— Logs a mood check-in
Run locally:
cd ionic_frontend
npm run smoke:playwright:stagingBackend Unit Tests
cd rails_api
# Model specs
bundle exec rspec spec/models/mood_log_spec.rb
# Interaction specs
bundle exec rspec spec/interactions/user_identity/add_mood_log_spec.rb
# GraphQL specs
bundle exec rspec spec/requests/mutations/add_mood_log_spec.rbFrontend Unit Tests
cd ionic_frontend
npm run test:unit -- --run -t "MoodCheckIn"
npm run test:unit -- --run -t "MoodSelector"GraphQL API Reference
Queries
Mood logs are returned as part of the user query:
query UserQuery($id: ID!) {
user(id: $id) {
moodLogs {
publicId
mood # "amazing" | "happy" | "calm" | "meh" | "tired" | "low"
note # Optional string (max 300 chars)
createdAtTime # Timestamp
goal { publicId name }
}
}
}Mutations
mutation AddMoodLog($mood: String!, $note: String, $goalId: ID) {
addMoodLog(mood: $mood, note: $note, goalId: $goalId) {
moodLog {
publicId
mood
note
createdAtTime
goal { publicId name }
}
errors
}
}Known Behaviors & Edge Cases
No daily limit: Users can log multiple mood check-ins per day. Each creates a separate
MoodLogrecord. There is no deduplication.Mood enum validation: The backend validates mood is one of the 6 allowed values. Invalid values return an error.
Note trimming: The note is trimmed before submission. Leading/trailing whitespace is removed. An all-whitespace note becomes null.
Goal linking is optional: If
goalIdis not provided (or "No goal" is selected), the mood log is saved without a goal association.Mood in goal events: When a mood is selected in the
GoalEventForm, it's stored on theGoalEventrecord (not as a separateMoodLog). These are different data paths — the mood check-in modal creates aMoodLog, while the goal update form stores mood on theGoalEvent.No edit/delete: Mood logs cannot be edited or deleted through the UI. They are immutable once created.
Character counter UX: The counter only appears below the textarea, not above. It shows
{current}/300format and does not turn red at the limit — themaxlengthattribute prevents overflow.
Last updated: 2026-05-22