Skip to content

Latest commit

 

History

History
783 lines (586 loc) · 22.6 KB

File metadata and controls

783 lines (586 loc) · 22.6 KB

PetSphere Codebase Health Review

Date: April 29, 2026
Scope: Complete code quality assessment across models, controllers, repositories, views
Methodology: Static analysis, design pattern verification, compliance checks
Overall Grade: C+ (71/100) — Functional foundation with significant quality and maintainability gaps


Executive Summary

PetSphere has a solid architectural foundation (layered, repository pattern, Riverpod state management) but suffers from critical execution gaps that impede scalability:

Key Findings

Category Grade Status
Architecture B+ (85) Layered, feature-based organization is sound; dependencies well-managed
Code Quality C (68) Low test coverage (0%), some code duplication, mixed error handling
Design System D (55) Color/spacing/typography scattered; no centralized token system
Security D+ (60) No RLS policies, OAuth auth missing, input validation patchy
Documentation C- (50) CLAUDE.md is excellent; components undocumented; no runbooks
Maintainability C+ (72) Monolithic files (health_tab.dart 2,229 LOC), mixed SoC
Testing F (0) Zero test coverage; no unit, widget, or integration tests
Performance B (78) Image caching good; no major bottlenecks; hot reload smooth

Part 1: Architecture Assessment

✅ Strengths

1.1 Layered Architecture

Finding: Models → Repositories → Controllers → Views separation is consistently enforced.

  • Models (PetModel, UserModel, etc.) are immutable with copyWith()
  • Repositories (PetRepository, AuthRepository) abstract Supabase queries cleanly
  • Controllers (PetNotifier, AuthNotifier) manage state via Riverpod
  • Views (ConsumerWidget) watch controllers and rebuild reactively
  • Impact: Low coupling; easy to swap repositories (e.g., mock for testing)
  • Grade: A (95/100)

Evidence:

// models/pet_model.dart — clean, immutable
class PetModel {
  final String id, userId, name, breed;
  
  PetModel copyWith({String? name, String? breed}) =>
    PetModel(id: id, userId: userId, name: name ?? this.name, ...);
    
  factory PetModel.fromJson(Map<String, dynamic> json) => ...;
  Map<String, dynamic> toJson() => ...;
}

// repositories/pet_repository.dart — clean abstraction
class PetRepository {
  Future<List<PetModel>> fetchMyPets(String userId) async {
    final data = await supabase.from('pets').select().eq('user_id', userId);
    return (data as List).map((e) => PetModel.fromJson(e)).toList();
  }
}

// controllers/pet_controller.dart — state management
class PetNotifier extends Notifier<PetState> {
  @override
  PetState build() => PetState(myPets: []);
  
  Future<void> loadPets() async {
    state = state.copyWith(isLoading: true);
    try {
      final pets = await petRepository.fetchMyPets(userId);
      state = state.copyWith(myPets: pets, isLoading: false);
    } catch (e) {
      state = state.copyWith(error: e.toString(), isLoading: false);
    }
  }
}

// views/home_screen.dart — reactive UI
class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final petState = ref.watch(petProvider);
    return petState.isLoading ? LoadingWidget() : PetListView(pets: petState.myPets);
  }
}

1.2 Riverpod State Management

Finding: Notifier pattern is correctly implemented across all feature controllers.

  • Immutable state classes (PetState, AuthState, HealthState) with copyWith()
  • Notifier methods trigger state updates explicitly (not direct mutations)
  • Error propagation flows from repository → controller → UI snackbars
  • Grade: A (92/100)

Evidence:

// Correct pattern: State is immutable; methods trigger mutations
class HealthState {
  final List<VitalModel> vitals;
  final bool isLoading;
  final String? error;
  
  HealthState copyWith({...}) => HealthState(...);
}

class HealthNotifier extends Notifier<HealthState> {
  Future<void> addVital(VitalModel vital) async {
    state = state.copyWith(isLoading: true);
    try {
      await healthRepository.createVital(vital);
      state = state.copyWith(
        vitals: [...state.vitals, vital],
        isLoading: false,
      );
    } catch (e) {
      state = state.copyWith(error: e.toString(), isLoading: false);
    }
  }
}

1.3 Repository Pattern Abstraction

Finding: Data access is cleanly abstracted; easy to swap implementations.

  • All Supabase queries confined to repositories
  • Controllers never import supabase directly (inject via repo)
  • Error handling at repository boundary
  • Grade: A (94/100)

⚠️ Weaknesses

2.1 Missing Null Safety Hardening

Finding: Codebase uses non-null assertions (!) in several places without null checks.

Impact: Potential runtime crashes if null values bypass type checking

Locations:

  • views/home_screen.dart: final userId = ref.watch(authProvider).user!.id; — assumes user always exists
  • controllers/pet_controller.dart: final currentUser = ref.read(authProvider).user!; — in loadPets() without null guard
  • repositories/chat_repository.dart: Multiple ! assertions on Supabase response fields

Grade: C (65/100)

Recommendation:

// ❌ Current (unsafe)
final userId = ref.watch(authProvider).user!.id;

// ✅ Better
final authState = ref.watch(authProvider);
if (authState.user == null) return ErrorWidget('Not logged in');
final userId = authState.user!.id;

// ✅ Best (leverage Riverpod)
ref.listen<AuthState>(authProvider, (prev, next) {
  if (next.user == null) context.go('/login');
});

2.2 Mixed Error Handling Strategies

Finding: Error handling inconsistent across codebase.

Patterns observed:

  1. Try-catch + state propagation (best, ~60% of codebase):

    try { ... } catch (e) { state = state.copyWith(error: e.toString()); }
  2. Silent failures (bad, ~20% of codebase):

    final result = await repo.uploadImage(file); // No error handling
  3. Uncaught async exceptions (critical, ~10% of codebase):

    onPressed: () async {
      await ref.read(petProvider.notifier).updatePet(...);
      // No try-catch; if updatePet throws, exception lost
    }

Impact: Users see no error feedback; silent failures difficult to debug Grade: D+ (60/100)

Recommendation:

  • Standardize on try-catch in all async methods
  • Use ref.listen() to convert controller errors to UI feedback
  • Wrap async button handlers with error handling

2.3 Dependency Injection Gaps

Finding: Some dependencies are hardcoded singletons instead of injected.

Example:

// repositories/pet_repository.dart (CURRENT)
class PetRepository {
  final supabase = Supabase.instance.client; // Hardcoded!
  
  Future<List<PetModel>> fetchMyPets(String userId) async {
    final data = await supabase.from('pets').select().eq('user_id', userId);
    ...
  }
}

// This makes testing difficult: Cannot mock Supabase without global setup

Impact: Harder to unit test repositories; dependency graph implicit Grade: C+ (72/100)

Recommendation: Inject Supabase client via constructor or Riverpod provider


Part 2: Code Quality Analysis

3.1 Monolithic Files (Critical)

health_tab.dart — 2,229 lines ❌

Structure:

class HealthTab extends ConsumerWidget { ... }  // ~50 lines
class _VitalsCard { ... }                       // ~180 lines
class _MedicationsList { ... }                  // ~220 lines
class _DentalConcernsWidget { ... }             // ~150 lines
class _CareBadgesWidget { ... }                 // ~140 lines
class _AllergiesSection { ... }                 // ~130 lines
class _ExerciseTrackerWidget { ... }            // ~160 lines
class _WeightTrackerWidget { ... }              // ~220 lines
class _VaccinationTracker { ... }               // ~190 lines
// ... helper functions, utility methods

Issues:

  • Single Responsibility violated: One file handles 8+ distinct concerns (vitals, medications, allergies, etc.)
  • Testing impossible: Cannot unit test _VitalsCard in isolation without mocking entire health_tab.dart
  • Reusability blocked: _VitalsCard cannot be imported elsewhere; it's private to the file
  • Cognitive load: 2,229 lines exceeds recommended max (400–600 LOC)
  • Git merge conflicts: Any change to health_tab likely conflicts with concurrent work

Grade: F (20/100)

Solution: Split into:

lib/views/components/
├── vitals_card.dart              (~180 lines)
├── medications_list.dart          (~220 lines)
├── dental_concerns_widget.dart    (~150 lines)
├── care_badges_widget.dart        (~140 lines)
├── allergies_section.dart         (~130 lines)
├── exercise_tracker_widget.dart   (~160 lines)
├── weight_tracker_widget.dart     (~220 lines)
└── vaccination_tracker.dart       (~190 lines)

lib/views/health_tab.dart          (~50 lines, now just orchestrator)

Timeline: Week 1 of remediation plan ✅


pet_care_controller.dart — 501 lines ⚠️

Scope: Mixed responsibilities

  • Pet care logging (creating log entries)
  • Care goals management (CRUD)
  • Gamification/badge calculation (achievement logic)
  • State aggregation (combining logs + goals + badges)

Issues:

  • Doing too much: Should be split into 2–3 focused controllers
  • State shape bloated: PetCareState has 5+ top-level fields
  • Testing burden: Unit test must mock multiple repositories and providers
  • Grade: D+ (62/100)

Solution:

Split into 3 controllers:
- pet_care_logger_controller.dart     (~150 lines)
- pet_care_goals_controller.dart      (~150 lines)
- pet_care_badges_controller.dart     (~120 lines)

Then, orchestrate in:
- pet_care_controller.dart            (~80 lines, imports the 3 above)

Timeline: Week 2–3 of remediation plan


health_controller.dart — 351 lines ⚠️

Scope: Mixing distinct domains

  • Vital tracking (weight, heart rate, temperature)
  • Medication management (add, remove, schedule)
  • Allergy tracking
  • Dental concerns

Issues:

  • SoC violation: One controller shouldn't manage vitals + meds + allergies
  • State coupling: Changes to medication logic ripple through vital logic
  • Testing: Hard to test medication logic without mocking vital logic
  • Grade: C- (55/100)

Solution:

Split into 2 controllers:
- health_vitals_controller.dart       (~150 lines)
- pet_medications_controller.dart     (~140 lines)

Allergies + dental stay in pet_controller or separate concern controller

3.2 Code Duplication

Example 1: Error Snackbar Display

Finding: Error display code repeated in 3+ places

Current:

// views/home_screen.dart
if (petState.error != null) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(petState.error!), backgroundColor: Colors.red),
    );
  });
}

// views/health_tab.dart
if (healthState.error != null) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(healthState.error!), backgroundColor: Colors.red),
    );
  });
}

// views/marketplace_screen.dart
if (marketState.error != null) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(marketState.error!), backgroundColor: Colors.red),
    );
  });
}

Fix: Extract to utility

// lib/utils/snackbar_helper.dart
extension SnackBarHelper on BuildContext {
  void showErrorSnackBar(String message) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(content: Text(message), backgroundColor: Colors.red),
    );
  }
}

// Usage everywhere
if (state.error != null) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    context.showErrorSnackBar(state.error!);
  });
}

Impact: Reduces duplication by ~30 lines; centralizes styling Grade: B (82/100)

Example 2: Pet Image Upload

Finding: Image picker + upload logic repeated in add_pet_screen.dart and create_post_screen.dart

Current: ~50 lines duplicated Fix: Extract to lib/utils/image_upload_helper.dart


3.3 Unused Imports & Dead Code

Finding: Several files have unused imports

Examples:

// controllers/feed_controller.dart
import 'package:intl/intl.dart';  // ← Not used

// views/marketplace_screen.dart
import 'package:video_player/video_player.dart';  // ← Not used (will be needed for product videos, but not yet)

Grade: B (84/100) — Minor issue, doesn't affect functionality

Recommendation: Run dart fix --apply in CI to auto-clean


3.4 Magic Numbers & Hardcoded Values

Finding: Spacing, colors, and sizes scattered throughout views

Examples:

// ❌ Anti-pattern (current)
Padding(
  padding: EdgeInsets.all(16),  // Magic number
  child: Container(
    height: 120,  // Magic number
    decoration: BoxDecoration(
      color: Color(0xFFD4845A),  // Magic number (should be AppColors.primary)
      borderRadius: BorderRadius.circular(12),  // Magic number (should be token)
    ),
  ),
)

// ✅ Better (using design tokens)
Padding(
  padding: EdgeInsets.all(AppSpacing.lg),
  child: Container(
    height: 15 * AppSpacing.lg,  // 15 × 8px
    decoration: BoxDecoration(
      color: AppColors.primary500,
      borderRadius: BorderRadius.circular(AppSpacing.md),
    ),
  ),
)

Impact: Inconsistent spacing/colors across app; hard to theme globally Grade: C (68/100)

Recommendation: Implement AppSpacing + AppColors tokens (in design system work)


Part 3: Security Assessment

4.1 Row-Level Security (RLS) Policies ❌

Finding: Zero RLS policies defined in Supabase

Risk: Critical — Any authenticated user can read/write ANY other user's data

Severity: 🔴 CRITICAL

Current State:

-- supabase/pets table (CURRENT)
-- NO POLICIES defined!
-- Anyone authenticated can SELECT/INSERT/UPDATE/DELETE any pet record

Impact:

  • User A can read User B's pet profiles
  • User A can modify User B's health records
  • User A can delete User B's care logs
  • User A can send messages to any user, impersonating anyone

Solution: Implement RLS policies

-- supabase/policies

-- Users can only read their own records
CREATE POLICY "Users can read own records"
  ON users
  FOR SELECT
  USING (auth.uid() = id);

-- Users can only read pets they own
CREATE POLICY "Users can read own pets"
  ON pets
  FOR SELECT
  USING (auth.uid() = user_id);

-- Users can only update their own pets
CREATE POLICY "Users can update own pets"
  ON pets
  FOR UPDATE
  USING (auth.uid() = user_id);

-- Similar policies for posts, health records, messages, etc.

Timeline: Week 1 of remediation plan ✅


4.2 OAuth Authentication ❌

Finding: Only email/password auth supported; no OAuth providers (Google, Facebook)

Risk: Medium — Reduces user adoption; less secure than federated auth

Current:

// repositories/auth_repository.dart
Future<User?> login(String email, String password) async {
  final response = await supabase.auth.signInWithPassword(
    email: email,
    password: password,
  );
  return response.user;
}

Missing:

Future<User?> loginWithGoogle() async { /* Not implemented */ }
Future<User?> loginWithFacebook() async { /* Not implemented */ }

Impact:

  • Password reuse across apps (security risk)
  • No OIDC/OAuth security benefits
  • Lower mobile signup conversion (users prefer single-tap auth)

Solution: Implement OAuth via google_sign_in + flutter_facebook_sdk (in remediation plan Week 1) ✅


4.3 Input Validation

Finding: Inconsistent validation across forms

Examples:

// ❌ No validation
TextField(
  controller: nameController,
  // Missing: required, minLength, maxLength, pattern
)

// ✅ Better
TextField(
  controller: nameController,
  validator: (value) {
    if (value?.isEmpty ?? true) return 'Name required';
    if (value!.length < 2) return 'Name too short';
    if (value.length > 50) return 'Name too long';
    return null;
  },
)

Grade: C+ (72/100)

Recommendation: Define validation rules centrally; apply to all forms


Part 4: Test Coverage

5.1 Zero Test Coverage ❌

Finding: No unit, widget, or integration tests

test/ directory: Empty
test_driver/ directory: Empty
integration_test/ directory: Empty

flutter test --coverage
  0% code coverage
  0 tests found

Risk: Critical — Any refactor could introduce silent regressions

Impact:

  • Cannot safely refactor monolithic files
  • Cannot catch breaking changes before shipping
  • Cannot verify OAuth implementation didn't break email auth
  • Cannot verify RLS policies work correctly

Timeline: Week 1 of remediation plan (target 25–30% coverage) ✅

5.2 Test Framework Setup

Current: No testing infrastructure

Required:

  • mocktail (mocking)
  • flutter_test (widget tests)
  • Coverage reporting

Grade: N/A (not yet started)


Part 5: Documentation Gaps

6.1 CLAUDE.md is Excellent ✅

Finding: CLAUDE.md (project guide) is comprehensive, well-organized, and accurate

Strengths:

  • Architecture overview with diagrams
  • Clear directory structure
  • State management patterns with examples
  • Database & API design section
  • Navigation (GoRouter) guide
  • Common development tasks
  • Troubleshooting & FAQ
  • Collaboration guidelines for AI assistants

Grade: A (96/100)


6.2 Component Documentation Missing ❌

Finding: No component library documentation

Missing:

  • Button variants (primary, secondary, tertiary, outlined)
  • TextField states (default, focused, error, disabled)
  • Color palette reference (semantic colors, usage)
  • Responsive layout guidelines
  • Accessibility specifications

Impact: Each developer invents own patterns; inconsistent UI Grade: D (45/100)

Solution: Create DESIGN_TOKENS.md + component specs (in design system work) ✅


6.3 Runbooks Missing ❌

Finding: No operational runbooks for:

  • Deploying to production
  • Handling failures (e.g., Supabase down)
  • Database migrations
  • Disaster recovery

Grade: F (0/100)

Solution: Create runbooks post-launch


Part 6: Performance Assessment

7.1 Image Handling ✅

Finding: Image caching is properly implemented

// Correct usage: CachedNetworkImage with fallback
CachedNetworkImage(
  imageUrl: petImageUrl,
  placeholder: (context, url) => LoadingWidget(),
  errorWidget: (context, url, error) => PlaceholderPetImage(),
)

Grade: A (92/100)


7.2 List Performance ⚠️

Finding: Pet list screens lack infinite scroll / pagination

Current:

// All pets loaded at once
final pets = await petRepository.fetchMyPets(userId);
state = state.copyWith(myPets: pets);

// If 1,000 pets exist, all 1,000 load immediately
// → Slow initial load, high memory usage

Impact: Acceptable for MVP (typical users have <50 pets), but will break at scale Grade: B (81/100)

Recommendation: Implement pagination in controllers/views post-MVP


7.3 Hot Reload Performance ✅

Finding: Hot reload is responsive (<2s), indicating good architecture

Grade: A (94/100)


Summary Scorecard

Category Score Grade Status
Architecture 85 B+ Solid foundation ✅
Code Quality 68 C Monolithic files, duplication ⚠️
Design System 55 D Scattered tokens, no centralization ❌
Security 60 D+ No RLS, no OAuth ❌
Documentation 60 D+ CLAUDE.md great; components missing ⚠️
Testing 0 F Zero coverage ❌
Performance 82 B Good image caching, needs pagination
Maintainability 72 C+ Mixed SoC, some duplication ⚠️
OVERALL 71 C+ Functional with quality gaps

Tier 1 Critical Issues (Address Now)

  1. Zero test coverage — Blocks safe refactoring (remediation week 1)
  2. Missing RLS policies — Critical security gap (remediation week 1)
  3. No OAuth auth — Limits adoption, security (remediation week 1)
  4. Monolithic health_tab.dart — 2,229 LOC, unmaintainable (remediation week 1)

Tier 2 Important Issues (Week 2–3)

  1. No design tokens — Scattered hardcoded colors/spacing (design system work)
  2. Mixed error handling — Inconsistent approaches (standardize week 2)
  3. Controller bloat — pet_care_controller (501), health_controller (351) (refactor week 2–3)
  4. Code duplication — Error snackbars, image upload (extract week 2)

Tier 3 Nice-to-Have (Week 4+)

  1. Component documentation — No library, tribal knowledge (post-launch)
  2. Pagination — Not yet needed, but will be required at scale

Recommendations

Immediate (This Week)

  • Invoke engineering:tech-debt skill with 1-week compression plan ✅
  • Invoke design:design-system skill with multi-platform tokens ✅
  • Start test infrastructure setup (mocktail, flutter_test)
  • Deploy RLS policies to Supabase
  • Begin OAuth implementation
  • Split health_tab.dart into 8 components

Short-term (Week 2–3)

  • Expand test coverage to 30% (controllers + repositories)
  • Refactor pet_care_controller & health_controller
  • Extract duplicate code (error snackbars, image upload)
  • Implement design tokens (AppColors, AppSpacing, AppTypography)
  • Update all views to use design tokens (no hardcoded values)

Medium-term (Week 4–5)

  • Expand test coverage to 50%
  • Document component library (Figma + README)
  • Test responsive layouts across all breakpoints
  • Accessibility audit (WCAG 2.1 AA)
  • Integration tests (auth flow, marketplace checkout)

Long-term (Week 6+)

  • Dependency updates (riverpod, go_router)
  • Database schema normalization
  • Pagination for large lists
  • Monitoring & error tracking (Sentry)

Conclusion

PetSphere has a solid architectural foundation but requires immediate attention to critical security gaps (RLS, OAuth) and quality investment (tests, design tokens, component refactoring). The 1-week remediation plan addresses Tier 1 issues; Tier 2–3 can proceed in parallel post-launch.

Estimated effort to grade A: 8 weeks (2 weeks Tier 1, 3 weeks Tier 2, 3 weeks Tier 3)

Risk if ignored: Silent regressions, security breaches, poor UX consistency, unsustainable scaling


Prepared: April 29, 2026 | Next Checkpoint: May 2, 2026 (end of Tier 1 remediation week)