MVP: תשתית FocusBalance עם Dexie, Tailwind, וניווט#1
Conversation
הקמת בסיס הפרויקט לפי האפיון ב-docs/FocusBalance.md: - שלב 0: Vite + React 18 + TypeScript + Tailwind 3, פלטה ייעודית (סגול #6B46C1, טורקיז #0D9488, כתום חם #EA580C — בלי אדום), גופן Heebo/Assistant, RTL מלא, ESLint, alias @/* ל-src. - שלב 1: מודל נתונים מלא לפי סעיף 5 (User, DoseEvent, Trigger, ReductionPlan), Dexie + IndexedDB עם אינדקסים מורכבים, repositories עם ולידציית NaN/Inf (כלל 4 ב-CLAUDE.md), seed אטומי של 15 טריגרים ברירת-מחדל ב-6 קטגוריות (כלל 2). - שלב 2: AppLayout מובייל-first עם ניווט תחתון ל-5 מסכים, FAB גלובלי לתיעוד מהיר (Zustand store), פוטר עם קישור קבוע ל"מתי לפנות לעזרה" (דרישה מסעיף 9), 6 מסכים stub, עמוד 404, עמוד עזרה עם מספרי חירום מבודדים LTR. הכל מקומי ב-IndexedDB — בלי backend, בלי שליחה לצד ג' (עיקרון הפרטיות מסעיף 8 באפיון). typecheck וגם build עוברים נקי. dev server מגיב 200. https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughFocusBalance introduces a Hebrew-first React+Vite app: project config and tooling, Tailwind design system, Dexie DB (schema, repositories, seeding), UI primitives and modal, onboarding and quick-log flows, dashboard cards/charts, pages/routing, and local-only IndexedDB storage. ChangesFocusBalance MVP - New React App
Sequence Diagram(s)sequenceDiagram
participant Browser
participant main_tsx as main.tsx
participant App
participant seedIfNeeded
participant db_triggers as db.triggers
participant Routes
participant Onboarding
participant AppLayout
Browser->>main_tsx: load /src/main.tsx
main_tsx->>App: mount with BrowserRouter
App->>seedIfNeeded: call on mount
seedIfNeeded->>db_triggers: check count
alt triggers empty
seedIfNeeded->>db_triggers: bulkAdd SYSTEM_TRIGGERS
end
seedIfNeeded-->>App: resolve (isReady=true)
App->>Routes: render Routes
alt user missing disclaimer
Routes->>Onboarding: render /onboarding (Welcome → Disclaimer → Profile)
else user onboarded
Routes->>AppLayout: render main routes (Dashboard, Insights, Plan, Journal, Settings, Help)
end
AppLayout->>AppLayout: render TopBar + Outlet + BottomNav + QuickLogFab + Footer
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 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 unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.gitignore:
- Around line 21-24: The .gitignore currently only ignores .env, .env.local and
.env.*.local and can miss other variants like .env.production or .env.staging;
update the ignore rules by replacing/augmenting those lines with a broader
pattern (e.g., add ".env.*" or ".env*") and explicitly re-include the example
file by adding an exception rule "!.env.example" so .env.* files are ignored but
.env.example remains tracked; adjust the entries around the existing ".env",
".env.local", and ".env.*.local" lines accordingly.
In `@index.html`:
- Around line 8-13: Remove the external Google Fonts preconnect and stylesheet
links (the <link rel="preconnect" href="https://fonts.googleapis.com">, <link
rel="preconnect" href="https://fonts.gstatic.com" crossorigin>, and the <link
href="https://fonts.googleapis.com/css2?family=Heebo...&family=Assistant...&display=swap"
rel="stylesheet"> entries) and replace them with self-hosted font files: add
Heebo and Assistant font files to your static assets (e.g., assets/fonts/),
create `@font-face` rules in your CSS that reference those local files
(woff2/woff), and update any font-family usages to use "Heebo" and "Assistant"
as defined in the `@font-face` blocks so browsers load fonts locally without
contacting Google.
In `@README.md`:
- Around line 30-43: The Markdown code fence in README.md that shows the folder
structure lacks a language tag (causing markdownlint MD040); update the opening
triple-backtick for that code block to include a language identifier (e.g.,
change ``` to ```text) so the block becomes ```text and re-run linting to
confirm the warning is resolved.
In `@src/db/repositories/doses.ts`:
- Around line 51-58: updateDoseEvent currently validates amountMg but not the
event timestamp, allowing invalid timestamps; add the same timestamp check used
in addDoseEvent (validate patch.timestamp with the existing timestamp validation
helper, e.g., isValidTimestamp or the same logic used in addDoseEvent) and throw
the same error if invalid, before calling db.doseEvents.update(id, patch).
Ensure you reference the patch.timestamp field and keep the existing amountMg
validation intact so both validations run in updateDoseEvent.
In `@src/db/repositories/users.ts`:
- Around line 14-25: upsertLocalUser currently validates doses but never checks
the scheduledTimes format, allowing invalid "HH:MM" values to be persisted; add
validation inside upsertLocalUser that iterates data.scheduledTimes (and handles
missing/empty) and asserts each entry matches a strict 24-hour "HH:MM" pattern
(e.g. regex for hours 00–23 and minutes 00–59) or parse-equivalent, and throw a
descriptive Error (e.g. 'scheduledTimes אינו בפורמט HH:MM') when any entry is
invalid so invalid times cannot be saved.
In `@src/styles/index.css`:
- Around line 12-14: Add a blank line before the `font-size: 16px;` declaration
in src/styles/index.css to satisfy the Stylelint rule
`declaration-empty-line-before`; locate the rule block where `@apply bg-surface
text-slate-900 antialiased;` and `line-height: 1.7;` are defined and insert an
empty line immediately above the `font-size` declaration so the declarations are
separated per `declaration-empty-line-before`.
- Around line 1-3: Stylelint is flagging valid Tailwind at-rules (`@tailwind`,
`@layer`, `@apply`) as unknown; update your Stylelint config (e.g., .stylelintrc.js
/ package.json stylelint section) to add an ignoreAtRules entry including
"tailwind", "apply", "layer" (or the full at-rule names) so the
at-rule-no-unknown rule ignores these directives; ensure the change is applied
where at-rule-no-unknown is inherited (from stylelint-config-standard-scss) so
the `@tailwind`, `@layer`, and `@apply` directives in src/styles/index.css are no
longer reported.
🪄 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: 4894a43d-8133-4287-9bc8-4836e6f7b91f
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (32)
.eslintrc.cjs.gitignoreREADME.mdindex.htmlpackage.jsonpostcss.config.jssrc/App.tsxsrc/components/layout/AppLayout.tsxsrc/components/layout/BottomNav.tsxsrc/components/layout/Footer.tsxsrc/components/layout/QuickLogFab.tsxsrc/components/layout/TopBar.tsxsrc/db/database.tssrc/db/repositories/doses.tssrc/db/repositories/triggers.tssrc/db/repositories/users.tssrc/db/schema.tssrc/db/seed.tssrc/lib/utils.tssrc/main.tsxsrc/pages/DashboardPage.tsxsrc/pages/HelpPage.tsxsrc/pages/InsightsPage.tsxsrc/pages/JournalPage.tsxsrc/pages/NotFoundPage.tsxsrc/pages/PlanPage.tsxsrc/pages/SettingsPage.tsxsrc/store/quickLogStore.tssrc/styles/index.csstailwind.config.jstsconfig.jsonvite.config.ts
מימוש סעיפים 4.6 ו-9 באפיון: - מסך אונבורדינג בן 3 שלבים (Welcome → Disclaimer → Profile) עם מחוון התקדמות. - מסך disclaimer מפורש: הצהרה שזה לא כלי רפואי, התחייבות להתייעצות עם רופא, מספרי חירום (מד״א/ער״ן) ב-LTR מבודד, וצ׳קבוקס שמחייב הסכמה לפני מעבר הלאה. - טופס פרופיל רפואי: מינון מרשם, מערך מינוני יחידה (דינמי עם הוספה/הסרה), מערך שעות מתוכננות (time picker עם dir="ltr"), שם רופא אופציונלי. ולידציית NaN/Infinity מלאה לפי כלל 4, deduplication של ערכים, מיון. - Route guard ב-App.tsx: כניסה לכל מסך פנימי דורשת hasAcceptedDisclaimer === true; אחרת ניתוב ל-/onboarding. המסלול ההפוך מנותב חזרה הביתה אם המשתמש כבר עבר אונבורדינג. - עמוד הגדרות אמיתי לעריכת הפרופיל בכל עת + הודעת "נשמר". - Hook ראקטיבי useLocalUser המבוסס על dexie-react-hooks לסנכרון אוטומטי בכל שינוי במסד. - רכיבי UI בסיסיים לשימוש חוזר: Input (עם תווית, רמז, שגיאה ו-aria-describedby) ו-Button (variants + sizes). typecheck וbuild נקיים; dev server מחזיר 200 גם ל-/ וגם ל-/onboarding. https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
מימוש סעיף 4.2 באפיון. המודל נפתח מה-FAB ומחזיק 4 שלבים עם מחוון התקדמות: - שלב 1 (מתי?): כפתור "עכשיו" ברירת-מחדל מול time picker בכיוון LTR; השעה ממומשת על תאריך היום. - שלב 2 (כמה?): chips של unitDoses מהפרופיל + שדה ידני. ולידציית NaN/Infinity לפני אישור (כלל 4 ב-CLAUDE.md). - שלב 3 (למה?): טריגרים מקובצים ל-6 קטגוריות עם אייקונים, ניתן לבחור מספר. אפשרות לדלג לחלוטין. שדה הערה אופציונלי. - שלב 4 (אישור): מסך "תועד" עם שני וריאנטים — סטנדרטי (ירוק-טורקיז עם ✓) או הודעה חמלתית כשמעבר ליעד (סגול עם 💜 וטקסט "ננתח את זה ביחד מחר"), בלי אדום. מציג סכום היום מול היעד. תוספות תומכות: - Modal נגיש כללי (Escape, נעילת גלילה, b-sheet במובייל). - doseHelpers: sumAmountMg עם הגנה מ-NaN, isPlannedDose עם סבילות ±60 דק' מול scheduledTimes, wouldExceedDailyTarget, והמרת HH:MM של היום ל-ISO. - חיבור QuickLogModal ל-AppLayout כדי שיהיה זמין מכל מסך. typecheck + build נקיים, dev server מחזיר 200. https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/onboarding/ProfileStep.tsx`:
- Around line 84-96: setIsSaving is only reset inside the catch, so after a
successful await onSubmit the saving state remains true and the form stays
locked; update the submit flow in ProfileStep.tsx to always clear the saving
flag by moving setIsSaving(false) into a finally block (or call it immediately
after the awaited onSubmit succeeds) so that setIsSaving(false) runs regardless
of success or error; keep existing error handling (setErrors) and retain the
call to onSubmit with prescribedDailyDose/unitDoses/scheduledTimes/doctorName.
In `@src/components/settings/ProfileForm.tsx`:
- Line 1: In ProfileForm.tsx, clear any existing success timeout before creating
a new one and ensure cleanup on unmount: add a ref (e.g., successTimeoutRef) to
hold the timeout id used when you call setShowSaved / setSaved (or inside
handleSave/handleSaveSuccess), call clearTimeout(successTimeoutRef.current)
before assigning a new setTimeout, store the id into successTimeoutRef.current,
and add a useEffect cleanup that clears the timeout on unmount to prevent stale
timers and premature hiding of the "נשמר" message.
In `@src/components/ui/Input.tsx`:
- Around line 14-15: The aria-describedby attribute references hintId even when
the hint element is not rendered due to the `hint && !error` check; update the
logic so described IDs are assembled only for actually rendered elements:
compute describedBy (or the aria-describedby value) from errorId (if error) and
hintId only when hint && !error, or move creation of hintId to be conditional
with the same `hint && !error` predicate; ensure the input's aria-describedby
includes only IDs that will be rendered (refer to hintId, errorId, inputId in
Input.tsx and the place where aria-describedby is set).
- Line 13: The fallback ID generation for the Input component (inputId = id ??
`input-${Math.random()...}`) is unstable and should be replaced with React's
useId(); import useId from React, call it inside the Input component (e.g.,
const generatedId = useId()) and set inputId = id ?? generatedId so the ID is
stable across SSR/hydration and re-renders; ensure all places that reference
inputId (label htmlFor, aria-describedby, etc.) keep using the new inputId
variable.
🪄 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: c5997692-3b57-4643-b2e1-7f3f83a60c22
📒 Files selected for processing (10)
src/App.tsxsrc/components/onboarding/DisclaimerStep.tsxsrc/components/onboarding/ProfileStep.tsxsrc/components/onboarding/WelcomeStep.tsxsrc/components/settings/ProfileForm.tsxsrc/components/ui/Button.tsxsrc/components/ui/Input.tsxsrc/hooks/useLocalUser.tssrc/pages/OnboardingPage.tsxsrc/pages/SettingsPage.tsx
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/quicklog/QuickLogModal.tsx`:
- Around line 80-99: handleSave currently clears selectedTriggers and notes via
setSelectedTriggers([]) and setNotes('') which are async and can allow stale
values to be persisted; fix by capturing the current selectedTriggers and notes
into local constants (e.g., const triggersToSave = selectedTriggers; const
notesToSave = notes.trim() || undefined) and use those locals when calling
addDoseEvent(...) inside handleSave, then clear state (call
setSelectedTriggers([]) and setNotes('')) only after the await addDoseEvent(...)
completes; apply the same change pattern for the other save path referenced (the
block around lines 303-309) so addDoseEvent always receives the captured values,
not potentially-stale state.
- Around line 80-88: handleSave can be called re-entrantly causing duplicate
inserts; add an early return if isSaving is true at the top of the handler.
Inside the handleSave function, check the existing isSaving state (the same
boolean that setIsSaving toggles) and return immediately when true before doing
any validation or state changes so concurrent invocations are ignored.
In `@src/components/ui/Modal.tsx`:
- Around line 19-31: Current useEffect handles Escape and body scroll but lacks
focus trapping and restoration; update the Modal component to trap focus while
open and restore focus to the previously focused element on close. In the
useEffect that runs when isOpen changes (the effect that defines handler, adds
'keydown' listener, and sets document.body.style.overflow), capture
document.activeElement into a variable (e.g., prevFocused) when opening,
programmatically move focus into the modal (to the first focusable element or a
designated element inside the dialog), intercept Tab/Shift+Tab in the 'keydown'
handler to keep focus within the modal (cycle focus between first and last
focusable elements), and on cleanup (when closing) remove the listener, restore
document.body.style.overflow to prevOverflow, and call prevFocused?.focus() to
restore focus to the trigger; use the Modal's container ref to locate focusable
elements.
In `@src/lib/doseHelpers.ts`:
- Around line 39-41: The equality check using Math.abs(minutes - scheduled)
doesn't account for midnight wrap-around; compute the minimal circular
difference between minutes and scheduled (e.g., let raw = Math.abs(minutes -
scheduled); let delta = Math.min(raw, 1440 - raw)) and replace the condition
with if (delta <= TOLERANCE_MIN) return true; reference the existing variables
scheduled, minutes and TOLERANCE_MIN so the function consistently matches
planned doses across midnight.
- Around line 53-62: todayTimeToIso currently only checks for NaN and relies on
Date.setHours which normalizes out-of-range values; update todayTimeToIso to
validate that the input splits into two parts and that hour is an integer
between 0 and 23 and minute is an integer between 0 and 59 (after
parsing/trimming) and throw the existing error message ('שעה לא תקינה') if any
check fails, only then call d.setHours(h, m, 0, 0) and return d.toISOString();
reference the todayTimeToIso function and the h/m variables when making the
changes.
🪄 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: 099776db-2c6d-4273-9de8-9f162b9aa5ec
📒 Files selected for processing (4)
src/components/layout/AppLayout.tsxsrc/components/quicklog/QuickLogModal.tsxsrc/components/ui/Modal.tsxsrc/lib/doseHelpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/layout/AppLayout.tsx
מימוש סעיף 4.1 באפיון. הדשבורד מציג עכשיו את הנתונים שנשמרו מ-Quick Log, ולא טקסט סטטי: - TodayCard: סכום מ"ג נוכחי / יעד, מד התקדמות שצבעו משתנה לפי המצב (ירוק/צהוב/כתום — בלי אדום), שעת מנה אחרונה, באדג' סטטוס. - StreakCard: רצף ימים ביעד עם חישוב שלא קוטע ביום שעוד לא התחיל בו תיעוד, אייקון מתחלף (Sprout → Flame ב-5+ ימים) וטקסט מעודד בלי לחץ. - WeeklyChart: בר-צ'ארט של 7 ימים עם Recharts, קו אופקי = יעד יומי, ציר X הפוך לכיוון קריאה עברית, ימים בעברית (א-ש), צבע עמודה לפי היחס ליעד. לחיצה מובילה לתובנות. - InsightCard: תובנת היום מבוססת נתונים — מזהה את יום השבוע השכיח של חריגות בחודש האחרון. דורש 5+ ימים של נתונים ולפחות 2 חריגות באותו יום כדי לקרוא לזה דפוס, אחרת מציג הודעה מעודדת. תוספות תומכות ב-doseHelpers: - classifyDayStatus + statusColors: מיפוי בלי אדום. - localDateKey + aggregateByDay: קיבוץ אירועים ליום לפי שעון מקומי (לא UTC) למניעת bug של "חצי לילה זז יום". - calculateStreak: חישוב רצף עם הגנה מ-365 ימים אחורה. - buildWeeklySeries: 7 נקודות לגרף. - getRecentDays: שאילתת טווח של N ימים אחרונים. הגנת NaN בכל פונקציות הסיכום (כלל 4 ב-CLAUDE.md). typecheck + build נקיים, dev server מחזיר 200. https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/lib/doseHelpers.ts (2)
53-62:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate
HH:MMbounds beforesetHours.On Lines 57-61, out-of-range values are accepted and normalized by
Date, which can shift the saved day/time unexpectedly.Suggested fix
export function todayTimeToIso(timeHHMM: string): string { - const [hStr, mStr] = timeHHMM.split(':'); - const h = Number(hStr); - const m = Number(mStr); - if (Number.isNaN(h) || Number.isNaN(m)) { + const [hStr, mStr, extra] = timeHHMM.split(':'); + const h = Number(hStr?.trim()); + const m = Number(mStr?.trim()); + const valid = + extra === undefined && + Number.isInteger(h) && + Number.isInteger(m) && + h >= 0 && + h <= 23 && + m >= 0 && + m <= 59; + if (!valid) { throw new Error('שעה לא תקינה'); } const d = new Date(); d.setHours(h, m, 0, 0); return d.toISOString(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/doseHelpers.ts` around lines 53 - 62, The function todayTimeToIso accepts timeHHMM and parses hStr/mStr into h and m but doesn't validate bounds, so Date.setHours will normalize out-of-range values and shift the day; update todayTimeToIso to explicitly validate that h is an integer between 0 and 23 and m between 0 and 59 (after parsing hStr and mStr), throwing the existing error ('שעה לא תקינה') for invalid values, and only then call d.setHours(h, m, 0, 0) and return d.toISOString().
39-41:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winHandle midnight wrap-around in planned-dose matching.
The check on Line 40 uses linear distance, so near-midnight planned doses can be missed (e.g.,
23:50vs00:10).Suggested fix
const scheduled = h * 60 + m; - if (Math.abs(minutes - scheduled) <= TOLERANCE_MIN) return true; + const diff = Math.abs(minutes - scheduled); + const circularDiff = Math.min(diff, 1440 - diff); + if (circularDiff <= TOLERANCE_MIN) return true;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/doseHelpers.ts` around lines 39 - 41, The current equality check uses linear minute distance and misses matches across midnight; replace the simple Math.abs(minutes - scheduled) comparison with a wrap-aware delta: compute raw = Math.abs(minutes - scheduled) and delta = Math.min(raw, 1440 - raw), then compare delta <= TOLERANCE_MIN (use the existing variables scheduled, minutes, and TOLERANCE_MIN). Update the conditional in the block containing scheduled to use this wrap-aware delta so planned doses like 23:50 vs 00:10 match correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/dashboard/InsightCard.tsx`:
- Around line 39-41: The weekday calculation uses new Date(key) which parses
"YYYY-MM-DD" as UTC and can yield the wrong local weekday; instead parse the key
into year/month/day and construct a local Date via new Date(year, monthIndex,
day) before calling getDay(). Replace the line creating d with code that splits
key (e.g., const [y, m, dStr] = key.split('-')), then const d = new
Date(Number(y), Number(m) - 1, Number(dStr)), and keep the rest (dow calculation
and overByWeekday update) unchanged.
In `@src/components/dashboard/StreakCard.tsx`:
- Around line 16-18: The streak calculation is artificially capped by calling
getRecentDays(60) so replace that limited fetch with a full-history fetch (e.g.
call getRecentDays() or a function that returns all days) so useLiveQuery(() =>
getRecentDays(), [], []) (or another unbounded/appropriate param) instead of
getRecentDays(60); keep downstream calls aggregateByDay(events) and
calculateStreak(byDay, user.prescribedDailyDose) unchanged so streaks reflect
the true history.
In `@src/components/dashboard/TodayCard.tsx`:
- Around line 92-96: The progressbar uses aria-valuenow={total} which can exceed
aria-valuemax={user.prescribedDailyDose}; clamp aria-valuenow to not exceed
user.prescribedDailyDose (and not drop below 0) before rendering. Update the
TodayCard component to compute a bounded value (e.g., const boundedTotal =
Math.min(Math.max(total, 0), user.prescribedDailyDose)) and use boundedTotal for
aria-valuenow (and optionally for any visual width calculations) so the
determinate progressbar always respects aria-valuemax.
---
Duplicate comments:
In `@src/lib/doseHelpers.ts`:
- Around line 53-62: The function todayTimeToIso accepts timeHHMM and parses
hStr/mStr into h and m but doesn't validate bounds, so Date.setHours will
normalize out-of-range values and shift the day; update todayTimeToIso to
explicitly validate that h is an integer between 0 and 23 and m between 0 and 59
(after parsing hStr and mStr), throwing the existing error ('שעה לא תקינה') for
invalid values, and only then call d.setHours(h, m, 0, 0) and return
d.toISOString().
- Around line 39-41: The current equality check uses linear minute distance and
misses matches across midnight; replace the simple Math.abs(minutes - scheduled)
comparison with a wrap-aware delta: compute raw = Math.abs(minutes - scheduled)
and delta = Math.min(raw, 1440 - raw), then compare delta <= TOLERANCE_MIN (use
the existing variables scheduled, minutes, and TOLERANCE_MIN). Update the
conditional in the block containing scheduled to use this wrap-aware delta so
planned doses like 23:50 vs 00:10 match correctly.
🪄 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: d782e71d-ac8d-4bed-9db3-c06a1ca127df
📒 Files selected for processing (7)
src/components/dashboard/InsightCard.tsxsrc/components/dashboard/StreakCard.tsxsrc/components/dashboard/TodayCard.tsxsrc/components/dashboard/WeeklyChart.tsxsrc/db/repositories/doses.tssrc/lib/doseHelpers.tssrc/pages/DashboardPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/db/repositories/doses.ts
Blueprint להפצה כ-Static Site: - buildCommand: npm ci && npm run build - publish: ./dist - Node 22 (Vite 5 דורש Node 20+) - SPA rewrite: /* → /index.html כדי ש-React Router יעבוד בנתיבים כמו /onboarding, /settings וכו' - כותרות אבטחה: X-Frame-Options, CSP מותאם (מאפשר רק Google Fonts כי הגופן Heebo נטען משם), HSTS, ועוד - cache ארוך לקבצי assets/* כי יש להם hash בשם https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
…zone
תיקונים תקפים מבדיקת הבוט:
- QuickLogModal:
* "דלג ותעד" שמר ערכי triggers/notes ישנים בגלל setState
אסינכרוני — handleSave עכשיו מקבל override מפורש
* הגנה מ-re-entrancy בלחיצה כפולה (early return אם isSaving)
- Input:
* החלפת Math.random ב-useId — מזהים יציבים בין רנדורים,
שומר על קישור label↔input ועל focus
* aria-describedby מציין רק IDs של אלמנטים שמרונדרים בפועל
- Modal:
* שמירת האלמנט שהיה בפוקוס לפני פתיחה, העברת פוקוס לתוך
הדיאלוג, והחזרה לטריגר בסגירה
- TodayCard:
* קיפוץ aria-valuenow ל-[0, יעד] כדי לא לחרוג מ-aria-valuemax;
aria-valuetext מציג את הערך האמיתי
- InsightCard:
* פירוק key (YYYY-MM-DD) ל-Date מקומי במפורש — מונע הסטה
ביום בשבוע באזורי זמן שליליים
- StreakCard:
* הגדלת חלון השאילתה ל-365 ימים, תואם לתקרת calculateStreak
- doseHelpers:
* isPlannedDose: הפרש מעגלי על מחוג 24 שעות (23:50 ↔ 00:10)
* todayTimeToIso: ולידציית טווחים מפורשת — setHours בולע
ערכים פסולים בשקט
- doses.ts:
* נורמליזציה של timestamp ל-UTC Z בכתיבה — מבטיח שאילתות
טווח לקסיקוגרפיות תקינות גם אם בעתיד יוזן offset
* ולידציית timestamp גם ב-updateDoseEvent (סימטרי ל-add)
- users.ts:
* ולידציה הגנתית של scheduledTimes מול regex של HH:MM
- ProfileStep:
* setIsSaving(false) ב-finally — בהצלחה ניווט קורה, אבל אם
מסיבה כלשהי לא — הטופס לא נתקע נעול
- ProfileForm:
* ניהול setTimeout דרך ref, ביטול בעת unmount או שמירה חוזרת
- .gitignore:
* הרחבת דפוס .env לכל הוריאנטים עם חריגה ל-.env.example
נדחו ביודעין (מצורף ב-thread):
- Bot טען על UTC/local mismatch בשאילתות — לא תקף, כל
ה-timestamps עוברים toISOString. הוספתי נורמליזציה הגנתית.
- Bot טען שכפתור "חזרה" עושה submit — Button כבר משתמש
ב-type="button" כברירת-מחדל (Button.tsx:23).
- Self-host של Google Fonts, README markdownlint, Stylelint
config, focus trap מלא של Tab/Shift+Tab — מחוץ לסקופ.
- שיתוף ולידציה ProfileStep/ProfileForm — ריפקטור, לא באג.
typecheck + build נקיים, dev server מחזיר 200.
https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 41a2a3d. Configure here.
| export function StreakCard({ user }: Props) { | ||
| // 365 ימים — תואם לתקרה הפנימית של calculateStreak כדי שרצף ארוך | ||
| // לא ייקטע ע"י חלון השאילתה | ||
| const events = useLiveQuery(() => getRecentDays(365), [], []); |
There was a problem hiding this comment.
Streak query window off-by-one clips long streaks
Low Severity
When today has no doses logged, calculateStreak starts from yesterday and can look back up to 365 days total (reaching day −366 from today). But getRecentDays(365) only fetches data from day −364 to today (365 calendar days). On the 365th backward step the date key won't exist in the map, the total defaults to 0, and the streak breaks one day early. Passing 366 instead of 365 would fully cover calculateStreak's ceiling.
Reviewed by Cursor Bugbot for commit 41a2a3d. Configure here.
מימוש סעיף 4.3 באפיון. בגרסה זו הטאב "טריגרים" פעיל ושלושת
האחרים הם placeholder אחיד "בגרסה הבאה":
- InsightsPage: ניווט טאבים נגיש (role=tablist/tab/tabpanel,
aria-selected). מתחיל ב-"טריגרים".
- TriggersTab: חישובים על 30 הימים האחרונים —
* Map of triggerId → count על כל ה-DoseEvents בתקופה.
* סיכום מילולי: הטריגר המוביל ואחוז משכיחות הכוללת
("דדליין — 40% מבחירות הטריגר, 12 פעמים").
* בר-צ'ארט אופקי של 8 טריגרים מובילים (Recharts), צבע ברנד
מודגש לטריגר #1.
* סיכום פר קטגוריה: צובר את ה-counts לפי category ומציג
המלצה אחת לכל אחת מ-3 הקטגוריות העיקריות. ההמלצות מקומיות
בקובץ — אין קריאה לשירות חיצוני.
* Empty state ידידותי אם עוד לא נבחרו טריגרים — מסביר איפה
בודקים בתהליך Quick Log.
- ComingSoonTab: רכיב placeholder אחיד לטאבי "דפוסי זמן",
"מגמה" ו"מתאמים" — מסבירים מה יהיה שם בגרסה הבאה.
חשוב: כל ההמלצות מסומנות במפורש כ"הצעות לחשיבה, לא תחליף
לייעוץ — שינוי מינון תמיד דרך הרופא" (תואם לעקרון המנחה
בסעיף 1 באפיון).
typecheck + build נקיים, /insights מחזיר 200.
https://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk


סיכום
הקמת תשתית MVP מלאה של FocusBalance — אפליקציית ווב לניהול והפחתה של מינון ריטלין. כל הנתונים נשמרים מקומית ב-IndexedDB (Dexie), ללא שליחה לשרת. הפרויקט כולל מודל נתונים מלא, ניווט תחתון, FAB לתיעוד מהיר, ו-6 עמודים stub.
שינויים עיקריים
מודל נתונים וDB
src/db/schema.ts– הגדרת TypeScript interfaces לכל הישויות:User,DoseEvent,Trigger,ReductionPlansrc/db/database.ts– מופע Dexie עם 4 טבלאות, אינדקסים לסינון מהיר, ו-LOCAL_USER_ID = 1(משתמש יחיד ב-MVP)src/db/seed.ts– טעינת 13 טריגרים ברירת-מחדל (משימה, קוגניטיבי, פיזי, רגשי, הרגל) בטרנזקציה אטומית (כלל 2)src/db/repositories/– שלוש repositories:users.ts–getLocalUser(),upsertLocalUser(),acceptDisclaimer()doses.ts–addDoseEvent(),getDoseEventsInRange(),getTodayDoseEvents(),deleteDoseEvent(),updateDoseEvent()triggers.ts–listTriggers(),listTriggersByCategory(),addCustomTrigger()ולידציה וביטחון
src/lib/utils.ts–isValidPositiveNumber()– בדיקת NaN, Infinity, וטווח (כלל 4)עיצוב וסטיילינג
tailwind.config.js– פלטה מותאמת: סגול עמוק (brand), טורקיז (accent), כתום חם (warm), עם צלליות רכות וגופנים עברים (Heebo, Assistant)src/styles/index.css– CSS בסיסי: רקע מנוקד, פוקוס נגיש, component classes (.card,.btn-primary,.btn-ghost,.chip)Layout וניווט
src/components/layout/AppLayout.tsx– מבנה הדף: TopBar, main content, FAB, BottomNav, Footersrc/components/layout/TopBar.tsx– כותרת עם לוגו ושם האפליקציהsrc/components/layout/BottomNav.tsx– ניווט תחתון עם 5 קישורים (בית, תובנות, תוכנית, יומן, הגדרות)src/components/layout/QuickLogFab.tsx– כפתור FAB כתום לתיעוד מהיר (מחובר ל-Zustand store)src/components/layout/Footer.tsx– קישור עדין ל"מתי לפנות לעזרהhttps://claude.ai/code/session_01FjJS1YsCyHPNyfQz6nuZBk
Summary by CodeRabbit
New Features
Documentation