refactor(web): adopt <DataState> in fizruk Workouts journal#1709
Conversation
Replace the `view === 'log' && !workoutsLoaded` skeleton guard with a unified <DataState> wrapper that owns the skeleton/content slot routing for the workout journal. - The `useWorkouts` hook flips `loaded` from false -> true after the first hydration tick (rehydrating from localStorage / SQLite). While `loaded` is false the DataState query feeds `data: undefined` and the skeleton slot renders; from the second tick on `data` is the real workouts list (possibly empty), so the journal can render its own empty state without flashing it during hydration. - `isLoading` mirrors the inverted `loaded` flag so a future stale-revalidate (cloud pull -> re-merge) keeps the journal visible rather than collapsing back to the skeleton. Behavior-preserving migration. Hooks/business logic untouched; presentational only. fizruk module has exactly one Skeleton-based loading site (this one) -- no other panels needed migration in this PR. Refs: docs/initiatives/0011-foundation-adoption-and-process-discipline.md (Phase 2, PR 2.5)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe ChangesWorkout Journal Rendering Pattern
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 0/10 reviews remaining, refill in 59 minutes and 48 seconds. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/modules/fizruk/pages/Workouts.tsx`:
- Around line 554-555: DataState is treating an empty workouts array as
empty-state and skipping WorkoutJournalSection; update the DataState usage
(which currently receives journalQuery) to only treat null/undefined as empty by
supplying a custom emptiness checker so an empty array still renders the journal
UI. Locate DataState with journalQuery and add an isEmpty (or equivalent prop
supported by DataState) that returns true only when data == null/undefined
(e.g., isEmpty: data => data == null) so WorkoutJournalSection sees an empty
workouts array and can render its own empty UI.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d4612d95-f6fd-4c02-9040-db57d05feba1
📒 Files selected for processing (1)
apps/web/src/modules/fizruk/pages/Workouts.tsx
| <DataState query={journalQuery} skeleton={workoutsLoadingSkeleton}> | ||
| {() => ( |
There was a problem hiding this comment.
DataState will treat empty workouts arrays as empty-state and skip WorkoutJournalSection.
With current props, once loading finishes and workouts is [], DataState default emptiness handling can render its own empty slot instead of the journal’s internal empty UI. That regresses the intended behavior described in this PR.
Suggested fix
- <DataState query={journalQuery} skeleton={workoutsLoadingSkeleton}>
+ <DataState
+ query={journalQuery}
+ skeleton={workoutsLoadingSkeleton}
+ isEmpty={() => false}
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <DataState query={journalQuery} skeleton={workoutsLoadingSkeleton}> | |
| {() => ( | |
| <DataState | |
| query={journalQuery} | |
| skeleton={workoutsLoadingSkeleton} | |
| isEmpty={() => false} | |
| > | |
| {() => ( |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/modules/fizruk/pages/Workouts.tsx` around lines 554 - 555,
DataState is treating an empty workouts array as empty-state and skipping
WorkoutJournalSection; update the DataState usage (which currently receives
journalQuery) to only treat null/undefined as empty by supplying a custom
emptiness checker so an empty array still renders the journal UI. Locate
DataState with journalQuery and add an isEmpty (or equivalent prop supported by
DataState) that returns true only when data == null/undefined (e.g., isEmpty:
data => data == null) so WorkoutJournalSection sees an empty workouts array and
can render its own empty UI.
⏱️ CI Pipeline Duration ReportBased on the last 50 successful runs on the default branch. Overall Pipeline
Trend (last 20 runs): Per-Job Breakdown
|
Update initiative 0011 to reflect Phase 2 progress through 2026-05-04: - Status header bumped to include the four open <DataState> consumer PRs: 2.4 #1703 (finyk), 2.5 #1709 (fizruk), 2.6 #1713 (nutrition), 2.7 #1714 (routine). Last-validated handle aligned with the actual initiative owner (@Skords-01). - Phase 2 table column 'Файли (приклад)' renamed to 'Файли (фактичні споживачі)' and ETA column replaced with Status (PR-link). Rows for 2.4–2.7 now point at the actual landed targets, not the initial guess names from the proposal draft (MonoTransactionsPanel, WorkoutHistoryPanel, NutritionMealsPanel, RoutineList, etc. — none of those components physically exist in the repo). - New explanatory note after the table records why the file list diverged from the original draft (real Skeleton-based loading sites per module) and documents the per-module finding for 2.6 / 2.7 (NutritionApp.tsx Menu plan branch; RoutineTimeline.tsx calendar branch — both modules have exactly one consumer of @shared/components/ui/Skeleton). Supersedes #1710 (single doc PR for 2.4 + 2.5 only); this combined update is the cleaner record because all four <DataState> consumer adoption PRs landed on the same day. Closes the doc-side of initiative 0011 PR 2.6 + 2.7.
Summary
Адаптуємо
<DataState>wrapper уfizruk/pages/Workouts.tsx— другий споживач (після PR 2.4) присутньої але до цього часу не використаної інфраструктури з PR 2.2. Замінюємо ad-hoc патернview === "log" && !workoutsLoaded ? <Skeleton /> : <WorkoutJournalSection />на<DataState>з контрактом «data === undefined→ skeleton;dataвизначено → content (навіть під час cloud-pull stale-revalidate)».apps/web/src/modules/fizruk/pages/Workouts.tsx): єдина точка з Skeleton-based loading state у fizruk модулі.useWorkoutsflip-аєloadedзfalse → trueпісля першого tick гідрації (з localStorage / SQLite). DataState query:data: workoutsLoaded ? workouts : undefined, isLoading: !workoutsLoaded. Це дозволяєWorkoutJournalSectionрендерити свою власну«порожньо»empty-state без миготіння під час mount.skeletonslot-ом.Поведінка-зберігаюча міграція. Hooks/business logic не змінюються — лише presentation.
Інші fizruk-сторінки (
Dashboard,Programs,Atlas,Body/*,Progress,Exercise) не мають Skeleton-based loading state — вони або працюють синхронно з local-first MMKV-web даними, або вже degrade-ять до empty defaults. Тому скоуп цього PR — рівно один файл (на відміну від 2.4, який торкав три).Governing Skill
sergeant-web-uisergeant-feature-deliveryPlaybook
docs/playbooks/web-feature-shipping.mdVerification
Результати локально:
typecheck— pass.eslint(точково на Workouts.tsx) — pass без warnings.vitest(src/modules/fizruk) — 15 files / 93 tests pass. Жодних регресій у workout-related тестах (useWorkouts.test.tsx,WorkoutJournalSection.finish.test.tsx, тощо).Без нового RTL-теста для
Workouts.tsx: компонент композитує 8+ хуків (useExerciseCatalog,useRecovery,useWorkouts,useMonthlyPlan,useWorkoutTemplates,useToast,useFizrukRestSound,useActiveFizrukWorkout) і переносний integration test потребував би мокати їх усі. DataState routing логіка вже покрита 11 тестами вapps/web/src/shared/components/ui/DataState.test.tsx, а існуючіuseWorkouts.test.tsx(4 tests) фіксують контрактloadedflag. Якщо рев'юер вважає за потрібне — можемо додати окремим follow-up.Additional checks:
data === undefinedtriggers skeleton; persisted data +isLoading=trueтримає content і не блимає у skeleton)Docs and Governance
AGENTS.mdneeded an update.Updated docs:
docs/initiatives/0011-foundation-adoption-and-process-discipline.mdбуде оновлено в окремому follow-up commit / PR після merge обох PR-ів (2.4 refactor(web): adopt <DataState> in finyk Mono panels #1703 і 2.5) — там відмітимо їх як done з лінками.AGENTS.md/ playbooks / governance / review docs без змін —<DataState>API вже задокументовано вapps/web/src/shared/components/ui/DataState.tsxJSDoc +DataState.stories.tsx.Risk and Rollout
workoutsLoadedзалишалосяtrueпісля гідрації, тож практично ніколи не повертало skeleton; цей invariant збережено).Hard Rule #15
AGENTS.mdbefore coding.--no-verify.Reviewer Notes
data: workoutsLoaded ? workouts : undefined. Семантика — точно та сама, що й попереднійview === "log" && workoutsLoadedguard, але виражена через єдину абстракцію.useWorkouts.loadedгарантовано flip-ається вtrueпісляuseEffectmount-а (підтвердженоuseWorkouts.test.tsxline 30 —await waitFor(() => expect(result.current.loaded).toBe(true))). Тож DataState skeleton slot бачимо лише на першому tick після mount.view === "home" / "catalog" / "templates",WorkoutsHome,WorkoutCatalogSection,WorkoutTemplatesSection) не має loading state і свідомо лишений поза DataState — це не RQ-driven UI.refactor(web): adopt <DataState> in finyk Mono panels) — refactor(web): adopt <DataState> in finyk Mono panels #1703.Summary by cubic
Adopted
<DataState>in the Fizruk Workouts journal to centralize loading and prevent the empty-state flash during hydration. Replaces the ad‑hocview === "log"skeleton guard without changing behavior.<DataState>from@shared/components/ui/DataStatewith:data === undefined→ skeleton; defineddata→ content (even whenisLoadingis true).data: workoutsLoaded ? workouts : undefinedandisLoading: !workoutsLoadedto show the list across stale‑revalidate.skeletonslot.apps/web/src/modules/fizruk/pages/Workouts.tsxonly.Written for commit 1d69985. Summary will update on new commits.
Summary by CodeRabbit