Fix streak creation error and refactor to Goals pattern#369
Fix streak creation error and refactor to Goals pattern#369
Conversation
…n streak creation
When users first enabled streak tracking, they saw an error toast saying 'Failed to create streak' even though the streak was successfully created. This was caused by Next.js redirect() throwing a NEXT_REDIRECT error that was caught by the client-side try-catch block.
Changes:
- Remove redirect('/streak') from server action in app/streak/page.tsx
- Add useRouter() hook to StreakOnboarding component
- Call router.refresh() after successful streak enable to reload server component
- Add router mock to StreakOnboarding tests to support useRouter()
- Add explanatory comments documenting why we use router.refresh() instead of redirect()
This follows the pattern already established in DesktopSidebar.tsx for logout handling. The fix ensures users see the correct success toast and the page refreshes to show the enabled streak without any error messages.
Fixes the 303 redirect error that occurred during streak creation.
…I routes) - Add enableStreak() method to streak API with types - Add enableStreakMutation to useStreak hook with toast notifications - Refactor StreakOnboarding to use useStreak hook (no props) - Create StreakPagePanel client wrapper (matches GoalsPagePanel pattern) - Simplify app/streak/page.tsx to server component with client wrapper - Update tests to mock useStreak hook instead of props - Remove server action and "use server" directive (consistency) Benefits: - Consistent with Goals pattern (React Query + API) - No NEXT_REDIRECT errors from server actions - Better state management and caching via React Query - Type-safe from API to UI - Easier to test with mocked hooks All 3815 tests passing.
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## develop #369 +/- ##
===========================================
- Coverage 78.07% 78.00% -0.07%
===========================================
Files 162 162
Lines 7289 7312 +23
Branches 1759 1765 +6
===========================================
+ Hits 5691 5704 +13
- Misses 1132 1139 +7
- Partials 466 469 +3
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 3 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…thod - Add 11 new tests for useStreak.enableStreak mutation - Tests for both enable and disable flows - Tests for loading states (isEnablingStreak) - Tests for success and error toasts - Tests for query invalidation on success - Tests for error handling with custom messages - Tests for parameter variations (with/without dailyThreshold) - Add 6 new tests for streakApi.enableStreak method - Tests for PATCH endpoint with correct parameters - Tests for enabling/disabling flows - Tests for response handling - Tests for error propagation from baseApiClient All 3832 tests passing. Increases coverage for new code in PR #369.
There was a problem hiding this comment.
Pull request overview
This PR fixes the misleading “Failed to create streak” toast caused by a server-action redirect() throwing NEXT_REDIRECT, and refactors streak onboarding to follow the existing Goals architecture pattern (API routes + React Query mutations).
Changes:
- Introduces typed
enableStreakrequest/response types and astreakApi.enableStreak()client method. - Adds an
enableStreakReact Query mutation touseStreak, and refactorsStreakOnboardingto use the hook directly. - Adds a new client wrapper (
StreakPagePanel) and simplifies/app/streak/page.tsxto a server component that delegates rendering.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/api/domains/streak/types.ts | Adds EnableStreak request/response types. |
| lib/api/domains/streak/api.ts | Adds streakApi.enableStreak() PATCH helper. |
| hooks/useStreak.ts | Adds React Query mutation for enabling/disabling streak tracking. |
| components/Streaks/StreakOnboarding.tsx | Refactors onboarding to call useStreak().enableStreak (no server action). |
| components/Streaks/StreakPagePanel.tsx | New client wrapper aligning streak page rendering with the Goals pattern. |
| app/streak/page.tsx | Moves server-side logic to fetch initial state + analytics and renders StreakPagePanel. |
| tests/unit/api-clients/streak.api.test.ts | Adds unit tests for the new enableStreak API helper. |
| tests/hooks/useStreak.test.ts | Adds hook-level tests for the new enable/disable mutation behavior. |
| tests/components/StreakOnboarding.test.tsx | Updates component tests to mock useStreak instead of passing onEnable props. |
| // Invalidate streak-related queries | ||
| queryClient.invalidateQueries({ queryKey: ['streak-analytics'] }); | ||
| queryClient.invalidateQueries({ queryKey: ['streaks'] }); | ||
| queryClient.invalidateQueries({ queryKey: ['dashboard'] }); |
There was a problem hiding this comment.
enableStreakMutation invalidates ['streak-analytics'], but the heatmap query uses a separate key (['streak-analytics-heatmap', 365]). If enabling/disabling also sets/changes the threshold, the heatmap can remain stale; consider invalidating the heatmap query key as well on success.
| toast.error("Failed to enable streak tracking. Please try again."); | ||
| setIsEnabling(false); | ||
| } | ||
| enableStreak({ streakEnabled: true, dailyThreshold: dailyGoal }); |
There was a problem hiding this comment.
After enabling streak tracking, the UI won’t transition out of onboarding because StreakPagePanel renders onboarding based on the server-provided streakEnabled prop, which won’t change until the route is refreshed/navigated. Consider triggering router.refresh() (or updating local client state / driving streakEnabled from a React Query GET) on successful enable so the analytics panel renders immediately.
| analyticsData = await streakService.getAnalytics(7, null); | ||
| } catch (error) { | ||
| logger.error({ error }, "Failed to fetch analytics"); | ||
| return ( | ||
| <div className="space-y-10"> | ||
| <PageHeader | ||
| title="Streak" | ||
| subtitle="Track your reading habits and progress over time" | ||
| icon={Flame} | ||
| /> | ||
| <div className="bg-[var(--card-bg)] border border-[var(--border-color)] p-8 text-center rounded-md"> | ||
| <p className="text-[var(--foreground)]/70 font-medium"> | ||
| Unable to load streak analytics. Please try again later. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| ); | ||
| // Pass undefined analyticsData to show error state | ||
| analyticsData = undefined; |
There was a problem hiding this comment.
This analyticsData flow relies on an untyped variable declared above (let analyticsData;), which effectively becomes any and loses type-safety when passed into StreakPagePanel. Please give analyticsData an explicit type (e.g., Awaited<ReturnType<typeof streakService.getAnalytics>> | undefined) to keep the server-to-client contract checked by TypeScript.
## Release v0.6.5 This release includes important bug fixes and improvements to date handling, reading goals, and streak functionality. ### Key Changes #### Bug Fixes - **Reading Goals (#373)**: Exclude DNF (Did Not Finish) books from reading goal calculations to accurately reflect completed books - **Date String Storage (#366)**: Enforce ADR-014 date string storage compliance across the entire codebase for consistent date handling - **Streak Creation (#369)**: Fix streak creation error and refactor to Goals pattern for better reliability #### Improvements - Enhanced date validation and storage consistency - Improved streak service reliability - Better reading goals accuracy - Comprehensive test coverage for new features ### Testing - All 99+ tests passing - New test coverage for date handling, reading goals, and streak functionality This release builds on v0.6.4 with critical fixes to ensure data integrity and accurate tracking. --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: OpenCode <opencode@sst.dev> Co-authored-by: TestForge <testforge@claude.ai> Co-authored-by: Mason <mason@sender.net> Co-authored-by: TestForge <testforge@tome.local> Co-authored-by: OpenCode <opencode@tome.local> Co-authored-by: OpenCode <opencode@anomaly.co> Co-authored-by: OpenCode <noreply@opencode.ai> Co-authored-by: AI Assistant <ai@opencode.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Mason Fox <masonfox@pop-os.lan> Co-authored-by: Mason Fox <masonfox@pop-os.tail3f4b5.ts.net> Co-authored-by: OpenCode <opencode@anomalyco.ai>
Summary
Fixes a bug where users see an error toast "Failed to create streak" when first enabling streak tracking, even though the streak is successfully created. Also refactors the streak onboarding flow to match the Goals pattern for better architectural consistency.
Problem
When users enable streak tracking:
redirect("/streak")which throws aNEXT_REDIRECTerror internallyStreakOnboardingSolution
Phase 1: Quick Fix (bb1dbf4)
redirect()from server actionrouter.refresh()call after successful enablePhase 2: Architectural Refactor (bb6cda9)
"use server"directive in the codebase for consistencyenableStreak()API method with proper typesenableStreakMutationto useStreak hook with toast notificationsStreakPagePanelclient wrapper component (matches GoalsPagePanel)StreakOnboardingto use hooks directly (no props)app/streak/page.tsxto server component patternChanges
Files Modified (7 total):
/lib/api/domains/streak/types.ts- Added EnableStreakRequest/Response types/lib/api/domains/streak/api.ts- Added enableStreak() method/hooks/useStreak.ts- Added enable/disable mutation with React Query/components/Streaks/StreakOnboarding.tsx- Refactored to use useStreak hook/components/Streaks/StreakPagePanel.tsx- NEW: Client wrapper component/app/streak/page.tsx- Simplified to server component with client wrapper/__tests__/components/StreakOnboarding.test.tsx- Updated to mock hookBenefits
Testing
Related
.specify/memory/patterns.md