Skip to content

Daily audio: subscription-driven Today view; stop client auto-regeneration#1152

Merged
mircealungu merged 14 commits into
masterfrom
daily-audio-subscription
May 29, 2026
Merged

Daily audio: subscription-driven Today view; stop client auto-regeneration#1152
mircealungu merged 14 commits into
masterfrom
daily-audio-subscription

Conversation

@mircealungu
Copy link
Copy Markdown
Member

Summary

Drives the Today view off the new first-class daily-audio subscription state instead of localStorage guesswork, and fixes two bugs along the way.

  • Stops unwanted auto-regeneration: removes the auto-generate useEffect + localStorage flags that silently regenerated a lesson whenever Today was shown empty (e.g. after finishing an older/paused lesson, or navigating back). The backend cron is now the source of truth for whether a lesson should exist; the client only generates on first-ever setup.
  • Fixes the mislabeled progress screen: the in-flight generation screen now takes its type/subject from the saved subscription, so a cron-generated lesson is no longer labeled "Three of Your Study Words" regardless of its real type.
  • Real status box (TodayEpisodeCard): "next lesson <date>", awaiting-engagement ("finish this to get the next"), turned-off state, and a turn-off control — replacing the phantom lessonData.paused flag and the hardcoded "tomorrow" copy.
  • Reconfigure no longer regenerates today — it applies from the next scheduled lesson (matches the "same-day generation = setup only" decision).

Dependency

Requires the matching API PR (adds subscription_status / awaiting_engagement / next_lesson_date to get_todays_lesson, and the set_daily_subscription_enabled endpoint). Merge/deploy API first.

Test plan

  • First-ever setup generates today's lesson with a correctly-labeled progress screen.
  • Finish an older/paused lesson, return to Today → no new generation is triggered.
  • Open the app mid-cron-generation → progress screen shows the correct type.
  • Status box: active-with-lesson, completed (next-lesson date), awaiting-engagement, off.
  • Turn off → "Daily lessons are off" + turn back on.
  • Confirm no zeeguu_* localStorage keys are written (DevTools → Application).

🤖 Generated with Claude Code

…ation

Drive the Today view off the new per-(user,language) daily-audio subscription
state (subscription_status / awaiting_engagement / next_lesson_date) returned by
get_todays_lesson, instead of localStorage guesswork.

- Remove the auto-generate effect + localStorage flags that could silently
  regenerate a lesson when landing on an empty Today (e.g. after finishing an
  older/paused lesson). The backend cron is now the source of truth for whether
  a lesson should exist; the client only generates on first-ever setup.
- Label the in-flight progress screen from the saved subscription type/subject
  (fixes cron-generated lessons being mislabeled "Three of Your Study Words").
- Status box shows real state: "next lesson <date>", awaiting-engagement
  ("finish this to get the next"), off, and a turn-off control.
- Reconfiguring no longer regenerates today's lesson; it applies from the next
  scheduled lesson.

Requires the matching API PR (new response fields + set_daily_subscription_enabled).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented May 29, 2026

Deploy Preview for voluble-nougat-015dd1 ready!

Name Link
🔨 Latest commit 587adcd
🔍 Latest deploy log https://app.netlify.com/projects/voluble-nougat-015dd1/deploys/6a1a16d4493ad50008d1424e
😎 Deploy Preview https://deploy-preview-1152--voluble-nougat-015dd1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Use the API's #643 `paused` (waiting lesson the learner hasn't engaged with) for
the badge + "listen to this one and they'll start again" copy, alongside the new
subscription_status/next_lesson_date. A paused lesson arrives as lessonData, so
the empty-Today path no longer needs an awaiting-engagement branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mircealungu and others added 12 commits May 29, 2026 10:44
…tate

When subscribed but no lesson exists for today (cron miss / first day / timezone),
offer an explicit generate button instead of a possibly-wrong "next lesson <date>"
— the API now returns next_lesson_date=null for this case. Replaces the silent
auto-generation that caused live regeneration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrate useDailyLessonPreference off /save_user_preferences for daily-audio
config — now uses /daily_subscription (GET) + /configure_daily_subscription
(POST), which talk to DailyAudioSubscription directly. Removes the dependency
on the legacy daily_audio_lesson_type_<lang> / _suggestion_<lang> preference
keys; those are still mirrored for one release for any client that hasn't
updated yet (legacy compat block in the API, marked for ~2026-06-05 removal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pair with the API's new `language` parameter on the daily-subscription and
get_todays_lesson endpoints. Send the lang the UI is currently displaying so a
fast language switch can't write the new topic / turn off / fetch today's
lesson against the wrong language while the server's user.learned_language
is still catching up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user doesn't have enough study words yet, the Vocabulary pill greys
out silently — leaving people guessing. Add a small inline note under the pill
row when autoDisabled is true, plus a title attr on the pill itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… the gap

The pill no longer greys out when the user is short on study words — they can
still select it. The inline note explains the prerequisite, and if they save
anyway the existing "not enough words" error path takes over (first-setup gen
fails with an actionable message; the cron silently skips until words appear,
matching the empty-Today recovery flow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ontrast

White on the brand amber was ~1.9:1 (well below WCAG AA's 4.5:1) and unreadable
in dark mode. Near-black on amber is ~11:1. Brand orange is the same in light
and dark mode, so a single text color works for both. Bumped font-weight to 600
while there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ulary tab

It was showing whenever Vocabulary was disabled, even when Topic/Situation was
the active pill — confusing context. Gate it on suggestionType === "auto" so it
only appears alongside the pill it explains.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… a notice

Reorder: description first ("what it is"), then the note ("why it's not ready
yet") — they read as a small contextual unit. Drop the floating italic and
replace with a left amber accent so it reads as a notice tied to the pill,
visually consistent with the brand color used on the Save button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pairs with API change that adds language_code to the progress record. The mount
adopt path skips the in-flight progress when it's for another language and
falls through to the normal getTodaysLesson. The poll path stops polling if it
sees a non-matching progress (we're watching the wrong generation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- #1 Polling no longer dies when dailyType resolves mid-generation. Read
  dailyType/dailySuggestion via refs in pollForProgress and drop them from the
  effect's deps; stopPolling's setIsGenerating(false) was being called by the
  deps-change cleanup, which then made the re-run return early.
- #2 turn-on/turn-off failures use toast.error so the user actually sees them
  (setError was invisible from the episode-card render). Add a toast.success
  for confirmation too.
- #3 alreadySubscribed also treats subscription_status === "active" as
  subscribed, closing the race where the dialog opened before the hook had
  loaded dailyType for an existing active subscriber.
- #4 Document the getTodaysLesson callback contract (always an object; check
  lesson_id) so the next caller can't write `if (!data)`.
- #5 formatNextLessonDate returns null for past dates instead of "today" —
  a backend regression emitting yesterday no longer reads as "today".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setDailySubscriptionEnabled + configureDailySubscription had a near-identical
fetch / response.ok / .json() / .then(callback) / .catch(Sentry+onError) block.
Pulled into a module-local postForm(api, path, formData, callback, onError, tag)
so each endpoint client is its FormData + a single helper call, and the next
endpoint (or a change to the error-handling shape) is one place to touch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mircealungu mircealungu merged commit ac2ea64 into master May 29, 2026
4 checks passed
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.

1 participant