Skip to content

feat(richness+streak): saturation pass + Duolingo streak + email/accountability scaffold#18

Open
Outtsett wants to merge 12 commits intodesign/ux-upgrade-screensfrom
design/visual-richness
Open

feat(richness+streak): saturation pass + Duolingo streak + email/accountability scaffold#18
Outtsett wants to merge 12 commits intodesign/ux-upgrade-screensfrom
design/visual-richness

Conversation

@Outtsett
Copy link
Copy Markdown
Owner

@Outtsett Outtsett commented May 4, 2026

Summary

Stacked on PR #17 — three orthogonal feature areas that ship together.

Visual richness

  • Saturation pass — palette shifted from conservative slate/sky/forest/amber to vivid indigo.600 / emerald.700 / orange.700 / rose.700 (Cyr-Head-Larios 2010 wellness-app engagement; Palmer-Schloss 2010 positive-outcome cluster; Hill 2008 dominance pair). All 32 WCAG AA contrast tests still pass.
  • Warm cream scaffold #F4EFE6 — replaces the "still looks white" slate.50 background. Light theme cards lift visibly off the cream; dark theme uses a deeper navy variant. Premium themes preserve their brand neutrals.
  • Filled inputs + chip/segmented theming — defaults applied to InputDecorationTheme, ChipThemeData, SegmentedButtonThemeData. No more bare-outlined-rectangle look.
  • SectionCard widget — soft-rounded card with colored leading-icon header used across forms.
  • AddHabit form — restructured into 7 SectionCards with semantic accents (Identity forest leading, How often / Reminder / Look indigo, Forgiveness / Health forest, 2-min-version warning amber). Mono digits on time 08:00 (22pt) + skip-tolerance 2 (36pt).
  • Onboarding — every page now has rich content beneath the hero icon: Welcome (3 pillar cards), Identity (animated vote-tally preview cycling 0→7 emerald dots), Reminders (iOS/Android dual cards), Health (Mindful + Workouts cards). Dot indicators replaced with a labeled progress band.
  • HomeScreen TodaySummaryHero — time-aware greeting + date + "X of Y done" body.

Duolingo-style daily streak

  • DailyStreak model (HiveType 13) + StreakTier enum (HiveType 14) — Bronze/Silver/Gold/Platinum/Diamond tiers map cleanly from currentStreak length.
  • StreakServiceonCompletion() is same-day-idempotent, increments on 1-day gap, consumes freezes on 2+day gap (1 freeze per missed day), awards 1 freeze per milestone (7/14/30/60/100/365), fires milestone listeners.
  • StreakFire widget — pulsing flame with mono digit count + tier label + freeze-count chip; tap → bottom sheet with longest / freezes / days-until-next-milestone + "About freezes" copy. Compact mode for AppBar use.
  • StreakMilestoneOverlay — full-screen 3.8s celebration on milestone hit: black 0.45 scrim + 60-particle radial confetti burst + tier-up announcement + "+1 freeze earned" chip + heavy haptic.
  • Sits alongside the existing per-habit skip-tolerant streak — they're orthogonal layers (forgiveness vs gamification), both surfaced in the UI.

Email + accountability + commitment device (local-only today)

  • EngagementPreferencesService — Hive-backed ChangeNotifier in the existing app_settings box (no new HiveType). Stores email opt-in flag + address + 4 sub-toggles (daily / atRisk / milestone / weekly), partner enable + name + email, commitment-device statement + miss-threshold slider (1-7 days).
  • Settings sections — 4 new at top: Daily streak (StreakFire snapshot), Email reminders, Accountability partner, Commitment device. Subtitles on opt-in rows clearly state "Backend delivery launches in v1.1".
  • docs/EMAIL_SYSTEM.md — 250-line design doc for the habit-email-hub Express service (Resend + react-email + Drizzle/SQLite, modeled exactly on the existing paypal-hub). Lists every email type, API surface, scheduling, audit, deployment plan, and privacy implications. Awaiting Tyler's sign-off before standing it up.

Hot-reload-no-relaunch rule

  • Added to global ~/.claude/CLAUDE.md and project CLAUDE.md.
  • Pattern: mkfifo /tmp/flutter_stdin; tail -f /tmp/flutter_stdin | flutter run -d chrome --web-port=8085 &. Then echo r > /tmp/flutter_stdin for reload, echo R for restart.
  • --web-port=8085 (not 8080 — Apache httpd is already bound on Tyler's box).

Test plan

  • flutter analyze --fatal-infos — clean
  • flutter test — 62 pass / 2 skip (preserved)
  • flutter build apk --debug — built
  • Manual web run — refresh shows warm cream surface, saturated palette, section cards, hero with StreakFire, all 4 onboarding pages with rich content
  • Manual: complete a fake 7-day streak in dev to verify milestone overlay + confetti
  • Manual: configure email + partner + commitment in Settings, restart app, verify persistence

Backend follow-up

Email + partner notifications need habit-email-hub. Design doc is ready; waiting on go-ahead before scaffolding the Express service in a separate repo.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 4, 2026 23:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bundles three feature tracks into the HabitDeveloper Flutter app: a saturated “visual richness” theming pass (tokens + scaffold background + filled inputs + SectionCard-driven forms), a new Duolingo-style whole-app daily streak system (with freezes + milestone celebration UI), and a local-only engagement-preferences scaffold (email reminders, accountability partner, commitment device) plus supporting documentation updates.

Changes:

  • Introduces DailyStreak/StreakService with StreakFire indicator + milestone overlay, and wires it into Home + Settings.
  • Adds EngagementPreferencesService and new Settings sections for email/accountability/commitment (local persistence only).
  • Updates design tokens and theming (warm cream scaffold background, filled inputs, chip/segmented styling) and restructures onboarding/add-habit UI accordingly.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
lib/design/tokens.dart Saturation-pass token ramps (indigo/emerald/orange/rose) and updated semantic palette mappings.
lib/models/app_theme.dart Warm cream / navy scaffold backgrounds; filled input defaults; chip + segmented theming updates.
lib/widgets/section_card.dart New reusable “SectionCard” UI container used for richer forms/sections.
lib/screens/add_habit_screen.dart Rebuilds AddHabit form into multiple SectionCard sections with richer styling/copy.
test/add_habit_screen_test.dart Updates widget test field indexing to match reorganized AddHabit form.
lib/models/daily_streak.dart Adds Hive-backed DailyStreak model and StreakTier enum.
lib/models/daily_streak.g.dart Adds adapters for DailyStreak/StreakTier (currently manually maintained).
lib/services/streak_service.dart Implements whole-app daily streak logic (completion-driven, freezes, milestones, listeners).
lib/widgets/streak_fire.dart New streak indicator widget + bottom-sheet detail UI.
lib/widgets/streak_milestone.dart New full-screen overlay celebration with confetti burst + milestone card.
lib/screens/home_screen.dart Adds TodaySummaryHero with StreakFire; updates completion flow to tick streak + show milestone overlay.
lib/services/engagement_preferences_service.dart New Hive-settings-backed ChangeNotifier for engagement preferences (email/partner/commitment).
lib/screens/settings_screen.dart Adds new sections: daily streak snapshot, email reminders, accountability partner, commitment device.
test/graceful_degradation_test.dart Extends “graceful degradation” test wiring to include streak + engagement services.
docs/EMAIL_SYSTEM.md Adds backend design doc for future habit-email-hub service.
lib/main.dart Registers new Hive adapters; opens streak box; adds providers for streak + engagement services.
lib/screens/onboarding_screen.dart Expands onboarding pages with richer content and replaces dot indicators with labeled progress.
CLAUDE.md Documents hot-reload stdin workflow and port convention for local dev.

Comment on lines +27 to +31
StreakService({required Box<DailyStreak> box, this.now}) : _box = box {
_state = _box.get(_singletonKey) ?? DailyStreak();
if (!_box.containsKey(_singletonKey)) {
_box.put(_singletonKey, _state);
}
Comment on lines +44 to +46
/// Current snapshot. Widgets should `context.watch<StreakService>()` and
/// read this. Don't mutate directly.
DailyStreak get state => _state;
Comment on lines +82 to +84
final last = DateTime.parse(_state.lastCompletionDayKey!);
final gap = today.difference(last).inDays;
if (gap == 1) {
Comment on lines +63 to +66
/// Called every time a habit is marked complete. Idempotent within
/// the same calendar day. Returns the milestone integer if a new one
/// was just crossed, else null.
Future<int?> onCompletion() async {
Comment on lines +1 to +10
// GENERATED CODE — DO NOT MODIFY BY HAND.
//
// Hand-authored on 2026-05-04 to avoid running build_runner on Windows
// without Developer Mode (build_runner -> .dart_tool symlink path that
// requires symlink privileges). Mirror the shape that
// `flutter pub run build_runner build` would emit for HiveType(13)
// + HiveType(14).
//
// Re-run `flutter pub run build_runner build --delete-conflicting-outputs`
// later if any @HiveField changes; that will overwrite this file.
Comment on lines +1134 to +1142
child: TextField(
controller: _name,
decoration: const InputDecoration(
labelText: "Partner's name",
hintText: 'Sam, Mom, Coach Ryan…',
),
onSubmitted: prefs.setPartnerName,
onEditingComplete: () => prefs.setPartnerName(_name.text),
),
Comment on lines +1249 to +1250
onEditingComplete: () =>
prefs.setCommitmentStatement(_statement.text),
Comment on lines +1206 to +1287
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_SectionHeader(
'Commitment device',
icon: Icons.gavel_rounded,
accent: tokens.warning,
),
Padding(
padding: const EdgeInsets.fromLTRB(
Spacing.s4,
0,
Spacing.s4,
Spacing.s2,
),
child: Text(
'Write a personal contract. We surface it at the moment '
'your streak is at risk — Ariely (2010) self-control '
'contract pattern.',
style: textTheme.bodySmall?.copyWith(
color: tokens.onSurfaceMuted,
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(
Spacing.s4,
0,
Spacing.s4,
Spacing.s3,
),
child: TextField(
controller: _statement,
maxLines: 3,
minLines: 2,
decoration: InputDecoration(
labelText:
'If I miss '
'${prefs.commitmentMissThreshold} days, I will…',
hintText:
'donate \$25 to Wikipedia / call Sam / no coffee for a week',
),
onSubmitted: prefs.setCommitmentStatement,
onEditingComplete: () =>
prefs.setCommitmentStatement(_statement.text),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(
Spacing.s4,
0,
Spacing.s4,
Spacing.s2,
),
child: Row(
children: [
Text('Trigger after', style: textTheme.bodyMedium),
Expanded(
child: Slider(
min: 1,
max: 7,
divisions: 6,
value: prefs.commitmentMissThreshold.toDouble(),
label: '${prefs.commitmentMissThreshold}',
activeColor: tokens.warning,
onChanged: (v) =>
prefs.setCommitmentMissThreshold(v.round()),
),
),
Text(
'${prefs.commitmentMissThreshold} '
'${prefs.commitmentMissThreshold == 1 ? "day" : "days"}',
style: AppTypography.mono(
color: tokens.warning,
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
],
),
),
],
Comment on lines +228 to +234
Row(
children: [
Icon(
Icons.local_fire_department_rounded,
size: 36,
color: tokens.warning,
),
Comment on lines +39 to +41
/// Freezes available — earned 1 per 7-day milestone (7/14/21/...). When
/// the streak would otherwise break (i.e. yesterday had no completion),
/// 1 freeze is automatically consumed and the streak survives.
Outtsett and others added 2 commits May 4, 2026 16:59
PageView gives each page a tall fixed-height slot, but _PageScaffold's
SingleChildScrollView + top-aligned Column took intrinsic height and
left a ~70% cream void below the action buttons on desktop web.

Wrap with LayoutBuilder + ConstrainedBox(minHeight: maxHeight) +
IntrinsicHeight so the column stretches to fill the slot, then center
content with mainAxisAlignment.center. Cap content at 560px wide so
the layout doesn't sprawl across desktop browsers. Falls back to
SingleChildScrollView when content overflows on small screens.

Affects all 4 onboarding pages — Welcome, Identity, Reminders, Health.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #19 (ci/automation-pipeline) added 4 new workflows + 1 labeler
config + 2 fleshed-out issue templates. Document the full GitHub
Actions surface (8 workflows × what they do × triggers) and the
current open-PR table so future sessions can pick up state without
re-discovering the automation footprint.

Includes the "branch protection action item" Tyler must do manually
in repo settings (4 required status checks) before dependabot-auto-
merge.yml is actually safe — the workflow only enables auto-merge,
the protection rules enforce the gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants