Skip to content

feat(mobile): time tracker history page#4341

Merged
vhpx merged 13 commits intomainfrom
feat/time-tracker-history-mobile
Feb 24, 2026
Merged

feat(mobile): time tracker history page#4341
vhpx merged 13 commits intomainfrom
feat/time-tracker-history-mobile

Conversation

@VNOsST
Copy link
Copy Markdown
Collaborator

@VNOsST VNOsST commented Feb 24, 2026

Description

  • Infinite scrolling for a time period
  • Navigate between time period
  • Integrate statistics for time period of sessions
  • Enhanced Edit Session with threshold aware flow

Screenshots for proof (must have)

image image image

Summary by cubic

Added a mobile Time Tracker history view with day/week/month navigation, infinite scroll, and a stats overview accordion. Also improved locale-aware first-day-of-week behavior, edit-session UX with threshold-aware actions, and tightened session API logic, loading states, and navigation.

  • New Features

    • Locale-aware first day of week across history controls and navigation; added i18n for history (empty state, overview, totals, load more/end).
    • Threshold-aware edit/pause/resume/stop with normalized workspace IDs and ISO timestamps; clearer invalid duration/unknown date messages and a “Session updated” toast.
  • Refactors

    • Safer initialization and preference loading: TimeTrackerPage/HistoryTab use MaterialLocalizations; TimeTrackerCubit restores history preferences, improves period navigation, adds optional error handling; stats fetching tolerates missing userId.
    • HistoryTab uses CustomScrollView with better loading/performance; HistoryStatsAccordion validates durations; StatsCards and TimerControls receive visual polish.
    • ShellPage gets compact labels, tuned icon sizes, faster apps-tab pointer handling, and more flexible key routing.
    • Session API modularized into action handlers with stronger permission checks, threshold chain validation, and schema-based ISO timestamp enforcement.

Written for commit 2a8ee02. Summary will update on new commits. Review in cubic

@VNOsST VNOsST self-assigned this Feb 24, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds paginated history and period statistics to the mobile time-tracker (models, repo, cubit/state, UI widgets, localization, tests) and introduces server-side session action handlers, Zod schemas, and threshold utilities to enforce edit/pause/stop policies.

Changes

Cohort / File(s) Summary
Data Models
apps/mobile/lib/data/models/time_tracking/period_stats.dart, apps/mobile/lib/data/models/time_tracking/session_page.dart
New immutable models: TimeTrackingPeriodStats, TimeTrackingPeriodBreakdown, and TimeTrackingSessionPage with JSON factories, defaults, and Equatable semantics.
Repository & API wiring
apps/mobile/lib/data/repositories/time_tracker_repository.dart
Adds getHistorySessions() and getPeriodStats() and imports above models; getStats now accepts optional userId.
Cubit & State
apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart, apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Adds HistoryViewMode, history fields, persistence for accordion state, history navigation/pagination methods, threshold-aware flows, many method signature updates to accept optional userId/throwOnError, and integration points to refresh history on edits.
History UI
apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart, apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart, apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
New HistoryPeriodControls and HistoryStatsAccordion; HistoryTab converted to StatefulWidget, adds infinite scroll, grouping-by-day rendering, period navigation, stats accordion toggle, and integrates with cubit APIs.
Edit Session UI
apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
Adds thresholdDays param and logic to disable/guard time edits when threshold exceeded, shows approval messaging, disables time pickers, and adds submission state handling.
Layout & small UI changes
apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart, apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart, apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
Makes stats cards responsive (Wrap), tweaks Add Missed Entry button style, computes/uses first-day-of-week helper when loading, and adjusts tab alignment and initialization.
Localization
apps/mobile/lib/l10n/arb/app_en.arb, apps/mobile/lib/l10n/arb/app_vi.arb
Adds timer/history and edit-restriction localization keys (labels, load more, total time, session updated, edit restriction/approval placeholder, validation keys).
Tests
apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart, apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
Adds cubit tests for history behavior (pagination, accordion persistence, threshold cases) and a widget test ensuring period controls remain visible during history loading.
Server API: sessions route & helpers
apps/web/src/app/api/v1/.../sessions/[sessionId]/route.ts, .../helpers.ts, .../actions/*.ts, .../schemas.ts, .../threshold.ts
Refactors session GET/PATCH/DELETE to dispatch to new action handlers; adds handleEditAction, handlePauseAction, handleResumeAction, handleStopAction, Zod schemas/types (patchSessionBodySchema, PatchSessionBody, etc.), and threshold helpers (checkSessionThreshold, getSessionChainRoot) enforcing edit/pause/stop policies and session-chain logic.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as HistoryTab
    participant Cubit as TimeTrackerCubit
    participant Repo as TimeTrackerRepository
    participant API as Backend
    participant Prefs as SharedPreferences

    User->>UI: open history view
    UI->>Cubit: loadHistoryInitial(wsId,userId,firstDayOfWeek)
    Cubit->>Prefs: read isHistoryStatsAccordionOpen
    Cubit->>Repo: getHistorySessions(dateFrom,dateTo,cursor?)
    Repo->>API: GET /sessions?dateFrom&dateTo&cursor
    API-->>Repo: TimeTrackingSessionPage (sessions, nextCursor, hasMore)
    Repo-->>Cubit: sessions, hasMore, nextCursor
    Cubit->>Repo: getPeriodStats(dateFrom,dateTo)
    Repo->>API: GET /stats?dateFrom&dateTo
    API-->>Repo: TimeTrackingPeriodStats
    Repo-->>Cubit: period stats
    Cubit-->>UI: emit state (historySessions, historyPeriodStats, accordion state)
    UI->>User: render controls, accordion, session list
Loading
sequenceDiagram
    actor User
    participant EditDialog as EditSessionDialog
    participant Cubit as TimeTrackerCubit
    participant Repo as TimeTrackerRepository
    participant API as Backend

    User->>EditDialog: open dialog
    EditDialog->>EditDialog: compute sessionAge vs thresholdDays
    alt exceeds threshold
        EditDialog->>User: disable time fields, show approval UI
    else within threshold
        EditDialog->>User: allow full edits
    end
    User->>EditDialog: Save
    EditDialog->>Cubit: editSession(sessionId, wsId, updates, userId, throwOnError)
    Cubit->>Repo: editSession API call
    Repo->>API: PATCH /sessions/[sessionId] { action: 'edit', ... }
    API-->>Repo: updated session or approval-required response
    Repo-->>Cubit: updated session
    Cubit->>Cubit: refresh recent sessions and period stats
    Cubit-->>EditDialog: success
    EditDialog->>User: show success toast, close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • vhpx
  • Adinorio

Poem

🐇
I hopped through weeks and days anew,
Tabs that jump and stats that view,
Old times gated, guarded tight,
Pages scroll the past just right,
Nibble, count, and leap — woohoo! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning PR description lacks required 'What?', 'Why?', and 'How?' sections; contains only a brief bullet-point summary and screenshots. Add structured 'What?', 'Why?', and 'How?' sections to the description following the repository template to explain the changes, their rationale, and implementation approach.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title 'feat(mobile): time tracker history page' clearly and concisely summarizes the main feature addition - a new history page for the mobile time tracker. It accurately represents the primary change in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/time-tracker-history-mobile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @VNOsST, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the time tracking feature by introducing a comprehensive history page. Users can now effortlessly browse their past sessions using infinite scrolling, navigate through different time periods like day, week, or month, and view aggregated statistics for each selected period. Additionally, the session editing experience has been refined to incorporate workspace-defined thresholds, ensuring that modifications to older sessions adhere to approval workflows, and providing immediate feedback on edit operations.

Highlights

  • Time Tracker History Page: Introduced a dedicated history page within the time tracker, allowing users to view past sessions with enhanced navigation and statistics.
  • Infinite Scrolling: Implemented infinite scrolling for time tracking sessions on the history page, enabling seamless loading of older entries as the user scrolls.
  • Period Navigation and Statistics: Added controls to navigate between different time periods (day, week, month) and integrated period-specific statistics, providing an overview of total time and session counts for the selected duration.
  • Enhanced Session Editing: Improved the session editing dialog to include threshold-aware logic, restricting time edits for older sessions that require approval and providing clear user feedback with toasts for success or failure.
  • Responsive Stats Cards: Refactored the statistics cards to use a Wrap layout, improving responsiveness and adapting to different screen sizes.
Changelog
  • apps/mobile/lib/data/models/time_tracking/period_stats.dart
    • Added TimeTrackingPeriodBreakdown and TimeTrackingPeriodStats models to represent period-specific time tracking statistics.
  • apps/mobile/lib/data/models/time_tracking/session_page.dart
    • Added TimeTrackingSessionPage model to handle paginated time tracking session data, including sessions, hasMore, and nextCursor.
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
    • Imported new period_stats.dart and session_page.dart models.
    • Added getHistorySessions method to fetch paginated historical time tracking sessions.
    • Added getPeriodStats method to retrieve time tracking statistics for a specified period.
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
    • Imported period_stats.dart, session_page.dart, and shared_preferences.dart.
    • Modified loadData to fetch history sessions and period stats alongside other initial data.
    • Added new methods setHistoryViewMode, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, refreshHistory, loadHistoryInitial, and loadHistoryMore to manage history page state and data loading.
    • Updated editSession, deleteSession, discardSession, and submitRequest to trigger loadHistoryInitial after successful operations.
    • Implemented toggleHistoryStatsAccordion to manage the visibility of history statistics and persist its state using SharedPreferences.
    • Added private helper methods _ensureHistoryPreferencesLoaded, _moveHistoryAnchor, _historyPeriodRange, and _loadRecentAndSummary for history logic and data fetching.
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
    • Imported period_stats.dart.
    • Added HistoryViewMode enum for day, week, and month views.
    • Extended TimeTrackerState with new properties for history management: historyViewMode, historyAnchorDate, historySessions, historyPeriodStats, historyNextCursor, historyHasMore, isHistoryLoading, isHistoryLoadingMore, and isHistoryStatsAccordionOpen.
    • Updated copyWith method to handle new history-related state properties and clear flags.
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
    • Wrapped the shad.Tabs widget in a Center widget for improved layout.
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
    • Imported threshold.dart utility.
    • Added thresholdDays parameter to EditSessionDialog constructor.
    • Introduced _isSubmitting state to manage button loading during save.
    • Displayed a warning message and restricted time editing if the session exceeds the workspace threshold.
    • Modified onSave callback to be async and included error handling with shad.showToast for session updates and errors.
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
    • Added a new widget HistoryPeriodControls to provide UI for selecting history view mode (day, week, month) and navigating between periods.
    • Implemented _SegmentTab as a sub-widget for the segmented control UI.
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
    • Added a new widget HistoryStatsAccordion to display time tracking period statistics in an expandable/collapsible card.
    • Included _Body and _SummaryStat sub-widgets for displaying detailed statistics and individual summary cards.
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
    • Converted HistoryTab to a StatefulWidget to manage ScrollController.
    • Implemented _handleScroll for infinite scrolling to load more history sessions.
    • Updated BlocBuilder to use state.historySessions instead of state.recentSessions.
    • Integrated HistoryPeriodControls and HistoryStatsAccordion into the history tab UI.
    • Added loading indicators for initial history load and loading more sessions.
    • Displayed messages for no sessions in the current period and end of list.
    • Modified _showEditDialog and _deleteSession to pass userId and handle asynchronous operations.
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
    • Refactored StatsCards to use LayoutBuilder and Wrap for responsive layout of stat cards.
    • Removed Expanded from _StatCard to allow flexible sizing within the Wrap.
  • apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart
    • Changed the 'Add missed entry' button from shad.GhostButton to shad.OutlineButton.
  • apps/mobile/lib/l10n/arb/app_en.arb
    • Added new localization keys: timerHistoryNoSessionsForPeriod, timerHistoryOverview, timerHistoryTotalTime, timerHistoryLoadMore, timerHistoryEndOfList, timerSessionUpdated, timerTimeEditingRestricted, and timerAllEditsRequireApproval.
  • apps/mobile/lib/l10n/arb/app_vi.arb
    • Added new Vietnamese localization keys: timerHistoryNoSessionsForPeriod, timerHistoryOverview, timerHistoryTotalTime, timerHistoryLoadMore, timerHistoryEndOfList, timerSessionUpdated, timerTimeEditingRestricted, and timerAllEditsRequireApproval.
  • apps/mobile/lib/l10n/gen/app_localizations.dart
    • Updated generated AppLocalizations abstract class with new history and session editing related getters and methods.
  • apps/mobile/lib/l10n/gen/app_localizations_en.dart
    • Updated generated English localization implementation with new history and session editing related strings.
  • apps/mobile/lib/l10n/gen/app_localizations_vi.dart
    • Updated generated Vietnamese localization implementation with new history and session editing related strings.
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
    • Registered DateTime as a fallback value for any() in mock setup.
    • Set mock initial values for SharedPreferences before each test.
    • Added a new test group for TimeTrackerCubit history behavior.
    • Included tests for changing history view mode and loading initial history.
    • Added tests for loadHistoryMore to verify appending of session results.
    • Added tests for toggleHistoryStatsAccordion to confirm persistence of preferences.
  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
    • Added a new test file for HistoryTab widget.
    • Included a test to ensure HistoryPeriodControls remain visible during history loading states.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
    • Imported normalizeWorkspaceId from workspace-helper.
    • Added normalizeWorkspaceId calls for wsId in PATCH and DELETE routes to ensure consistent workspace ID handling.
    • Passed request object to createClient and getPermissions calls for proper context.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 24, 2026

🌐 i18n Check Report

✅ All i18n checks passed!

Check Status
🔤 Translation Sorting ✅ Sorted
🔑 Translation Keys ✅ All in sync
🔄 Key Parity (en ↔ vi) ✅ Keys in sync
📦 Namespace Check (shared → apps) ✅ All present
🤖 Auto-generated by i18n Check workflow • Last updated: 2/24/2026, 8:24:53 AM

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
track Skipped Skipped Feb 24, 2026 6:10am

@vercel vercel Bot temporarily deployed to Preview – track February 24, 2026 03:22 Inactive
@VNOsST VNOsST marked this pull request as ready for review February 24, 2026 03:22
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 24, 2026

🔧 Biome Check Report

Total Issues Found: 0

🔧 Format Check Results
Metric Value Status
📁 Files Checked 4486 ✅ Complete
Errors 0 ✅ None
⚠️ Warnings 0 ✅ None
ℹ️ Info 0 ✅ None
📝 Total Issues 0 ✅ Passed
🔍 Lint Check Results
Metric Value Status
📁 Files Checked 4491 ✅ Complete
Errors 0 ✅ None
⚠️ Warnings 0 ✅ None
ℹ️ Info 0 ✅ None
📝 Total Issues 0 ✅ Passed

🎉 All Issues Resolved!

Your code is now 100% clean! Great job! 🏆

🤖 Auto-generated by Biome Check workflow • Last updated: 2/24/2026, 8:24:24 AM

@coderabbitai coderabbitai Bot added calendar enhancement New feature or request platform Infrastructure changes tudo labels Feb 24, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive time tracker history page for the mobile app, which is a great addition. The implementation includes infinite scrolling, period navigation, and statistics, all of which are well-structured within the BLoC pattern. The enhanced edit session flow, which now respects workspace thresholds, is a significant improvement for data integrity and user experience. I've identified a bug in the month navigation logic that can lead to incorrect date calculations and a minor performance improvement opportunity in data fetching. Overall, this is a solid contribution.

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 20 files

Confidence score: 3/5

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts may still 404 for slug/alias wsId because session lookup doesn’t use normalizedWsId, which is a concrete user-facing regression risk.
  • Mobile month navigation in apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart can jump months when the day doesn’t exist in the target month, which could confuse users moving across months.
  • Overall risk is moderate because there’s at least one user-impacting bug in the web API path and a navigation edge case on mobile.
  • Pay close attention to apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts - session lookup should use the normalized workspace id.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart:180">
P3: The new _formatSeconds duplicates the existing formatter in stats_cards.dart. Consider extracting a shared time-formatting helper to avoid divergence when formatting rules change.</violation>
</file>

<file name="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts">

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts:214">
P2: The new normalization isn’t applied to the session lookup, so requests with a slug/alias `wsId` will pass membership checks but still 404 when fetching the session. Use `normalizedWsId` for the session query to keep workspace scoping consistent.</violation>
</file>

<file name="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart:683">
P2: Month navigation can skip the intended month when the current day doesn’t exist in the target month (e.g., Jan 31 → Mar 2). Clamp the day to the last day of the target month before constructing the new DateTime.</violation>

<violation number="2" location="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart:751">
P2: The calls to `_repo.getSessions` and `_repo.getStats` are awaited sequentially despite being independent. This doubles the latency compared to running them in parallel with `Future.wait`, which was the pattern used elsewhere in this codebase before this refactor.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart (1)

288-295: ⚠️ Potential issue | 🟡 Minor

Inconsistent abs() usage produces misleading text for negative durations.

When _endTime is before _startTime, d.inHours is negative but d.inMinutes.abs() % 60 is positive, so a -90 min duration displays as "-1h 30m" (implying -30 min). Although the Save button is disabled via _isValid, the duration text is still shown to the user.

Consider either applying abs() consistently with a leading minus sign, or displaying a placeholder like "Invalid" when _endTime ≤ _startTime.

Proposed fix
  String _formatDuration(Duration d) {
+   if (d.isNegative) return '--';
    final h = d.inHours;
    final m = d.inMinutes.abs() % 60;
    final s = d.inSeconds.abs() % 60;
    if (h > 0) return '${h}h ${m}m';
    if (m > 0) return '${m}m ${s}s';
    return '${s}s';
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart`
around lines 288 - 295, The duration formatter _formatDuration currently mixes
negative hours with absolute minutes/seconds causing misleading output; update
_formatDuration to detect negative durations (d.isNegative) and either return a
clear placeholder like "Invalid" when used for cases where _endTime ≤ _startTime
(use alongside existing validation with _isValid/_startTime/_endTime), or
consistently format a negative duration by taking Duration.abs() and prefixing
the whole string with '-' (e.g., '-1h 30m'); implement one of these approaches
in _formatDuration so the UI no longer shows misleading mixed-sign times.
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (4)

94-94: 🧹 Nitpick | 🔵 Trivial

Avoid any for chainSummary.

chainSummary is typed as any, which defeats type safety. Consider using the ChainSummary interface already defined in this file, or ChainSummary | null.

♻️ Suggested fix
-  let chainSummary: any = null;
+  let chainSummary: ChainSummary | null = null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
at line 94, Replace the loose any typing on the variable chainSummary with the
proper interface type: change its declaration to use the existing ChainSummary
type (e.g., ChainSummary | null) so the variable is strongly typed; update its
initialization and any assignments/usages in the same file (route.ts) to respect
the new type (handle null cases or narrow to ChainSummary before accessing
properties) and ensure imports/definitions of ChainSummary in this file are
used.

249-250: 🧹 Nitpick | 🔵 Trivial

Missing Zod validation for request body.

The PATCH handler destructures the request body without any runtime validation. Per coding guidelines, external inputs at API boundaries should be validated with Zod. The action field drives significant branching logic, and each branch destructures additional fields (breakTypeId, title, startTime, etc.) without type or presence checks, which could lead to subtle bugs or exploitable edge cases.

Consider defining a discriminated union schema with Zod (discriminated on action) and validating early, returning 400 on failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 249 - 250, The PATCH handler currently reads request.json() and
destructures action (and later fields like breakTypeId, title, startTime)
without runtime validation; add a Zod schema (a discriminated union on action)
that defines the allowed shapes for each action variant and validate the parsed
body against it at the top of the handler, returning a 400 error when validation
fails; then replace direct destructuring with the validated result so downstream
branches (the code paths that reference breakTypeId, title, startTime, etc.) get
properly typed/validated input.

253-259: ⚠️ Potential issue | 🔴 Critical

Bug: Session lookup uses raw wsId instead of normalizedWsId.

Line 257 queries .eq('ws_id', wsId) with the raw route parameter, while every other query in this handler correctly uses normalizedWsId. If wsId is a special identifier (e.g., personal or internal), this will never match a database row (which stores UUIDs), causing all PATCH operations to return 404.

🐛 Fix: use normalizedWsId
     const { data: session, error: sessionError } = await supabase
       .from('time_tracking_sessions')
       .select('*')
       .eq('id', sessionId)
-      .eq('ws_id', wsId)
+      .eq('ws_id', normalizedWsId)
       .eq('user_id', user.id)
       .single();

Based on learnings: "Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 253 - 259, The session lookup uses the raw route param wsId instead
of the normalized UUID, causing misses for special identifiers; update the
Supabase query that builds session (the await
supabase.from('time_tracking_sessions').select(...).eq('id',
sessionId).eq('ws_id', wsId).eq('user_id', user.id).single()) to use
normalizedWsId in the .eq('ws_id', ...) check (i.e., .eq('ws_id',
normalizedWsId)), ensuring normalizedWsId is available in scope before the
query.

207-831: 🧹 Nitpick | 🔵 Trivial

PATCH handler is ~625 lines with deeply nested action branches — consider extracting.

This single handler manages stop, pause, resume, and edit actions, each with substantial logic. The file overall is ~904 LOC, well above the 400 LOC guideline. Consider extracting each action into its own function (e.g., handleStopAction, handlePauseAction, etc.) or splitting into separate route files if the framework supports it.

As per coding guidelines: "Files >400 LOC and components >200 LOC should be refactored into smaller, focused units."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 207 - 831, The PATCH handler is too large and deeply nested;
extract the action branches into focused helper functions to reduce file size
and improve readability. Create separate functions like handleStopAction({
sbAdmin, supabase, session, sessionId, normalizedWsId, user, canBypass,
requestBody }) , handlePauseAction({ sbAdmin, supabase, session, sessionId,
normalizedWsId, user, canBypass, requestBody }) , handleResumeAction({ sbAdmin,
session, sessionId, normalizedWsId, user }) , and handleEditAction({ sbAdmin,
session, sessionId, normalizedWsId, user, canBypass, requestBody }) that
encapsulate the DB queries, RPC calls, threshold checks (e.g.,
checkSessionThreshold), break handling, and response shaping; keep PATCH
responsible only for auth, workspace normalization, permission checks, session
lookup, and delegating to the appropriate handler which returns a NextResponse
or throws for the outer try/catch to handle. Ensure each helper returns the same
shape (NextResponse.json({ session, ... })) so callers can simply return its
result.
apps/mobile/lib/features/time_tracker/widgets/history_tab.dart (1)

197-215: ⚠️ Potential issue | 🟡 Minor

DateTime.now() fallback for null startTime silently misgroups sessions.

Lines 202 and 209 use DateTime.now() when session.startTime is null. This places such sessions in "today's" group regardless of their actual date, which can produce confusing UI. Consider filtering out sessions with null start times, or using a dedicated "Unknown date" group.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/time_tracker/widgets/history_tab.dart` around lines
197 - 215, In _groupByDay, avoid using DateTime.now() for null
TimeTrackingSession.startTime (which misgroups them); instead treat null
startTime sessions as a separate "unknown" bucket: when iterating sessions, if
session.startTime == null use a fixed key like 'unknown' and add to that group,
and when mapping groups to _DayGroup produce a label 'Unknown date' for the
'unknown' key while continuing to format other keys with dateFmt.format on
first.startTime!.toLocal(); ensure you reference _groupByDay,
TimeTrackingSession.startTime, and _DayGroup when making the change.
🤖 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/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart`:
- Around line 696-740: The localAnchor reconstruction in _historyPeriodRange
rebuilds anchor using DateTime(...) which is redundant for already-local
DateTimes and will incorrectly shift instants if anchor is UTC; replace the
manual field constructor with anchor.toLocal() when the goal is to normalize UTC
inputs, or simply stop reconstructing and use anchor directly (update uses of
localAnchor inside _historyPeriodRange and keep HistoryViewMode branches
unchanged).
- Around line 747-757: The helper _loadRecentAndSummary currently returns
state.stats when userId is null/empty which can yield stale UI data; change it
to always obtain up-to-date stats from the repository (call _repo.getStats(wsId,
userId) even when userId is null/empty) and return that fresh TimeTrackerStats
instead of falling back to state.stats, or if returning cached stats was
intentional add a brief inline comment in _loadRecentAndSummary explaining why
state.stats is used for the null/empty userId case (reference symbols:
_loadRecentAndSummary, _repo.getStats, TimeTrackerStats, state.stats).
- Around line 280-338: The catch block in loadHistoryInitial swallows repository
exceptions after emitting an error state; update loadHistoryInitial to rethrow
the caught Exception (or add a boolean throwOnError parameter like editSession
and rethrow only when true) so callers can react to failures; locate the
loadHistoryInitial method in TimeTrackerCubit, emit the same error state in the
on Exception catch (e) branch and then throw e (or conditionally throw when
throwOnError is true), keeping the existing state updates intact.
- Around line 678-694: The month branch in _moveHistoryAnchor produces invalid
month rollovers when current.day exceeds the target month's length; update the
HistoryViewMode.month case in _moveHistoryAnchor to compute the target
year/month, determine the target month's last day via DateTime(targetYear,
targetMonth + 1, 0).day, clamp the day to min(current.day, lastDay), then
construct the new DateTime using that clamped day while preserving current's
hour/minute/second/millisecond/microsecond so month navigation from dates like
Jan 31 correctly yields Feb 28/29 instead of rolling into March.
- Around line 300-316: The current parallel call uses Future.wait and then
unsafe casts of results[0] and results[1] to TimeTrackingSessionPage and
TimeTrackingPeriodStats; replace the Future.wait([...]) pattern around
_repo.getHistorySessions(...) and _repo.getPeriodStats(...) with Dart 3's record
`.wait` extension so you can await the tuple and destructure directly (e.g.,
final (page, periodStats) = await (...).wait) to preserve types and remove the
manual casts; update usages of page and periodStats accordingly and delete the
interim results variable and casts.

In `@apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart`:
- Around line 85-100: historyAnchorDate is declared as DateTime? in copyWith but
lacks the sentinel/clear pattern, so it cannot be reset to null; update the
copyWith signature to accept either the sentinel pattern (change the parameter
type to Object? historyAnchorDate = _sentinel) or add a bool
clearHistoryAnchorDate flag and update copyWith logic to set historyAnchorDate
to null when the sentinel is passed or clearHistoryAnchorDate is true; modify
the copy logic branches that currently read historyAnchorDate to respect the
sentinel/clear flag and return null appropriately, and apply the same change for
the analogous nullable fields noted around lines 125-140 (use the same sentinel
or clear flag pattern) so all nullable fields can be explicitly cleared.

In `@apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart`:
- Around line 326-368: The async calls to showDatePicker and showTimePicker in
edit_session_dialog.dart can resume after the widget is disposed, so add a
mounted check after each await: immediately after the await showDatePicker(...)
and after await showTimePicker(...), insert if (!context.mounted) return; before
calling onChanged/constructing the new DateTime; this prevents onChanged from
being invoked on a disposed widget (affecting the buttons that use enabled,
value and onChanged).
- Around line 228-269: Replace uses of the deprecated State.mounted checks with
context.mounted after each await in the save handler: after the await
widget.onSave(...) and before showing toasts or calling navigator.pop verify if
(!context.mounted) return; and in the exception handler do the same; also avoid
exposing raw exception text in the UI by replacing error.toString() in the
Alert.destructive content with a generic localized message (e.g.,
context.l10n.commonSomethingWentWrong or a dedicated
context.l10n.timerSessionUpdateFailed) when calling shad.showToast from the
catch block for the onSave call; keep the rest of the logic around
_isSubmitting, widget.onSave, shad.showToast and navigator.pop unchanged.

In `@apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart`:
- Around line 30-31: Move the eager DateFormat.MMMd() computations into the
branch that needs them: the week view. Currently weekStartLabel and weekEndLabel
are computed unconditionally; instead compute them lazily inside the switch/case
handling viewMode == ViewMode.week (or the widget's week branch) using
period.start and period.end so they are only created when used. Update any
references to weekStartLabel/weekEndLabel to use the new local variables inside
that branch and remove the top-level declarations.
- Around line 127-134: The week-start is hardcoded to Monday in the
HistoryViewMode.week branch; change the logic to use a configurable or
locale-derived first day of week (e.g., a firstDayOfWeek int that you obtain
from MaterialLocalizations.firstDayOfWeekIndex or a passed-in config) and
compute the start using a safe modular offset: replace the subtraction of
(anchor.weekday - DateTime.monday) with a modular calculation using
firstDayOfWeek so the start Calculation (and resulting start and end values
returned by the HistoryViewMode.week case) respect the locale/configured first
day of week; update the code that calls this switch or the surrounding widget to
provide firstDayOfWeek if needed.
- Around line 143-189: _SegmentTab currently uses GestureDetector which provides
no accessibility semantics, focus traversal, or keyboard interaction; replace or
wrap the GestureDetector with an accessible interactive widget. Either wrap the
AnimatedContainer (or the whole child) in a Semantics(widget) with button: true
and a meaningful label (use the existing label prop, e.g., "Segment: $label")
and onTap/ onLongPress semantics, and also add FocusableActionDetector or
InkWell to provide focus and keyboard activation tied to the onTap callback;
ensure visual focus indicator and that onTap is invoked for keyboard
(Enter/Space) as well as touch.

In `@apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart`:
- Around line 180-185: _formatSeconds currently returns "0m" for durations under
60 seconds; update the function (named _formatSeconds) to display seconds for
sub-minute durations by checking if totalSeconds < 60 and returning
'${totalSeconds}s', otherwise keep the existing minutes/hours formatting (hours
via totalSeconds ~/ 3600, minutes via (totalSeconds % 3600) ~/ 60) so short
durations render as seconds instead of "0m".

In `@apps/mobile/lib/features/time_tracker/widgets/history_tab.dart`:
- Around line 75-191: The ListView currently builds all children eagerly (the
ListView with controller _scrollController and the children list that includes
HistoryPeriodControls, HistoryStatsAccordion, grouped.map(...) producing
SessionTile widgets, and optional load-more/footer widgets), which is
inefficient for paginated/large histories; convert this to a lazily-built scroll
view by replacing the ListView(children: [...]) with either a CustomScrollView
containing SliverList/SliverToBoxAdapter for the static controls and a
SliverList.builder for the variable grouped/session items, or implement a
ListView.builder that flattens your sections into an indexable list (compute a
flattened itemCount and in itemBuilder return the correct widget for headers
(entry.label), SessionTile (session with categoryColorById[session.categoryId]
and callbacks _showEditDialog/_deleteSession), loading indicators, and the "Load
More" button that calls cubit.loadHistoryMore); keep using the same
_scrollController and preserve existing widgets: HistoryPeriodControls,
HistoryStatsAccordion, SessionTile, and the load-more footer so behavior and
callbacks remain unchanged.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart`:
- Around line 104-110: The value Text in stats_cards.dart currently uses
theme.typography.small with only fontWeight changed; update it to use
theme.typography.p.copyWith(fontWeight: FontWeight.w700) for stronger hierarchy
and consistency with _OverviewCard, and ensure the accompanying label Text uses
theme.typography.textSmall (replace any theme.typography.small used for the
label) so the value and label match the established card-based stats pattern.

In `@apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart`:
- Around line 98-142: The test relies on the initial historyViewMode being week
which makes the call to setHistoryViewMode('ws-1','user-1',
HistoryViewMode.month) change state; add an explicit precondition assertion to
ensure the test actually exercises the change: before invoking
cubit.setHistoryViewMode assert that cubit.state.historyViewMode is not
HistoryViewMode.month (e.g., expect(cubit.state.historyViewMode,
isNot(HistoryViewMode.month))) so the test will fail loudly if defaults change
and you can still validate the load logic exercised by setHistoryViewMode.

---

Outside diff comments:
In `@apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart`:
- Around line 288-295: The duration formatter _formatDuration currently mixes
negative hours with absolute minutes/seconds causing misleading output; update
_formatDuration to detect negative durations (d.isNegative) and either return a
clear placeholder like "Invalid" when used for cases where _endTime ≤ _startTime
(use alongside existing validation with _isValid/_startTime/_endTime), or
consistently format a negative duration by taking Duration.abs() and prefixing
the whole string with '-' (e.g., '-1h 30m'); implement one of these approaches
in _formatDuration so the UI no longer shows misleading mixed-sign times.

In `@apps/mobile/lib/features/time_tracker/widgets/history_tab.dart`:
- Around line 197-215: In _groupByDay, avoid using DateTime.now() for null
TimeTrackingSession.startTime (which misgroups them); instead treat null
startTime sessions as a separate "unknown" bucket: when iterating sessions, if
session.startTime == null use a fixed key like 'unknown' and add to that group,
and when mapping groups to _DayGroup produce a label 'Unknown date' for the
'unknown' key while continuing to format other keys with dateFmt.format on
first.startTime!.toLocal(); ensure you reference _groupByDay,
TimeTrackingSession.startTime, and _DayGroup when making the change.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Line 94: Replace the loose any typing on the variable chainSummary with the
proper interface type: change its declaration to use the existing ChainSummary
type (e.g., ChainSummary | null) so the variable is strongly typed; update its
initialization and any assignments/usages in the same file (route.ts) to respect
the new type (handle null cases or narrow to ChainSummary before accessing
properties) and ensure imports/definitions of ChainSummary in this file are
used.
- Around line 249-250: The PATCH handler currently reads request.json() and
destructures action (and later fields like breakTypeId, title, startTime)
without runtime validation; add a Zod schema (a discriminated union on action)
that defines the allowed shapes for each action variant and validate the parsed
body against it at the top of the handler, returning a 400 error when validation
fails; then replace direct destructuring with the validated result so downstream
branches (the code paths that reference breakTypeId, title, startTime, etc.) get
properly typed/validated input.
- Around line 253-259: The session lookup uses the raw route param wsId instead
of the normalized UUID, causing misses for special identifiers; update the
Supabase query that builds session (the await
supabase.from('time_tracking_sessions').select(...).eq('id',
sessionId).eq('ws_id', wsId).eq('user_id', user.id).single()) to use
normalizedWsId in the .eq('ws_id', ...) check (i.e., .eq('ws_id',
normalizedWsId)), ensuring normalizedWsId is available in scope before the
query.
- Around line 207-831: The PATCH handler is too large and deeply nested; extract
the action branches into focused helper functions to reduce file size and
improve readability. Create separate functions like handleStopAction({ sbAdmin,
supabase, session, sessionId, normalizedWsId, user, canBypass, requestBody }) ,
handlePauseAction({ sbAdmin, supabase, session, sessionId, normalizedWsId, user,
canBypass, requestBody }) , handleResumeAction({ sbAdmin, session, sessionId,
normalizedWsId, user }) , and handleEditAction({ sbAdmin, session, sessionId,
normalizedWsId, user, canBypass, requestBody }) that encapsulate the DB queries,
RPC calls, threshold checks (e.g., checkSessionThreshold), break handling, and
response shaping; keep PATCH responsible only for auth, workspace normalization,
permission checks, session lookup, and delegating to the appropriate handler
which returns a NextResponse or throws for the outer try/catch to handle. Ensure
each helper returns the same shape (NextResponse.json({ session, ... })) so
callers can simply return its result.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03d6608 and 76bb803.

⛔ Files ignored due to path filters (3)
  • apps/mobile/lib/l10n/gen/app_localizations.dart is excluded by !**/gen/**
  • apps/mobile/lib/l10n/gen/app_localizations_en.dart is excluded by !**/gen/**
  • apps/mobile/lib/l10n/gen/app_localizations_vi.dart is excluded by !**/gen/**
📒 Files selected for processing (17)
  • apps/mobile/lib/data/models/time_tracking/period_stats.dart
  • apps/mobile/lib/data/models/time_tracking/session_page.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart
  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Build Android APK (Development)
  • GitHub Check: build / build
  • GitHub Check: Build iOS (Development, simulator)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Playwright E2E
  • GitHub Check: Run tests and collect coverage
  • GitHub Check: Verify generated types
  • GitHub Check: Type Check
  • GitHub Check: Deploy-Preview
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (19)
apps/mobile/lib/l10n/arb/*.arb

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain ARB localization files in lib/l10n/arb/ with both English and Vietnamese translations

Files:

  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
apps/mobile/lib/l10n/**/*.{arb,dart}

📄 CodeRabbit inference engine (GEMINI.md)

When updating apps/mobile/lib/l10n/arb/*.arb, regenerate or update apps/mobile/lib/l10n/gen/* so tracked outputs stay in sync.

Files:

  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
apps/mobile/lib/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

apps/mobile/lib/**/*.dart: Use flutter_bloc with Cubits for feature state management in Flutter app
Use go_router with auth-aware redirects and ShellRoute for bottom tabs in Flutter app
Use supabase_flutter with flutter_secure_storage for token persistence in Flutter app
Follow very_good_analysis linting rules in Flutter app
Use on Exception catch (e) instead of bare catch in Flutter code; avoid catching Error subclasses
Capture context dependencies before first await and guard BuildContext usage after await with if (!context.mounted) return; in Flutter
Never return from a finally block in Dart/Flutter code
Use Future<void> Function() callbacks for mutation-driven UI actions in Flutter; await before closing dialogs

apps/mobile/lib/**/*.dart: When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the @ check and any email-specific success note (use TextInputType.emailAddress or an explicit parameter).
Use on Exception catch (e) (or specific exception types) instead of bare catch, avoid catching Error subclasses like TypeError, capture context dependencies before the first await, guard BuildContext usage after await with if (!context.mounted) return;, and do not return inside finally blocks.
The Flutter mobile app uses BLoC/Cubit state management, go_router navigation, and supabase_flutter for auth. Build flavors: main_development.dart, main_staging.dart, main_production.dart.
For mutation-driven Flutter UI actions (approve/reject/update), use Future<void> Function() callbacks (not VoidCallback), await them before closing dialogs/sheets, and surface failures in the UI (e.g., SnackBar).

apps/mobile/lib/**/*.dart: Use on Exception catch (e) with specific exception types in Flutter; avoid untyped catch blocks
Check context.mounted before using BuildContext after await in Flutter to preven...

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart
  • apps/mobile/lib/data/models/time_tracking/period_stats.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/data/models/time_tracking/session_page.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
apps/mobile/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Always run bun check:mobile after changes to apps/mobile/. This runs dart format --set-exit-if-changed lib test && flutter analyze && flutter test. All three checks MUST pass. If it reports a Dart format failure, rerun to confirm a clean pass.

apps/mobile/**/*.dart: Run dart format --set-exit-if-changed lib test in apps/mobile/ to verify code formatting; fix with dart format lib test
Run bun check:mobile (dart format + flutter analyze + flutter test) after making changes to apps/mobile/

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart
  • apps/mobile/lib/data/models/time_tracking/period_stats.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/data/models/time_tracking/session_page.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
apps/mobile/test/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Wrap Flutter widget tests rendering shadcn_flutter components with shad.ShadcnApp including shad.ShadcnLocalizations.delegate

Wrap Flutter widgets using shadcn_flutter with shad.ShadcnApp in tests to provide theme context

Files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
apps/mobile/**/test/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Wrap widgets that use shadcn_flutter in shad.ShadcnApp with shad.ShadcnLocalizations.delegate so shad.Theme.of(context) resolves in tests.

Files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript with explicit return types for exported functions
Prefer interface for defining object shapes in TypeScript
Use TanStack Query (React Query) for ALL client-side data fetching - NEVER use useEffect for data fetching
Always include cache: 'no-store' in fetch() calls within TanStack Query queryFn to prevent dual-layer caching conflicts
Use Server Components by default; add 'use client' only when necessary
Use @tuturuuu/ui/sonner for toast notifications instead of the deprecated @tuturuuu/ui/toast
Refactor files exceeding 400 LOC and components exceeding 200 LOC into smaller, focused units
Extract complex logic to separate utilities and custom hooks following single responsibility principle
Import database types from packages/types/src/db.ts instead of manually defining database types (only after user runs migrations via bun sb:push and typegen via bun sb:typegen)
Always use stable array query keys with structure: [domain, subdomain?, paramsHash, version?] in TanStack Query
Use discriminated unions over enums for TypeScript type definitions
Narrow unknown/any at boundaries; justify with comments if needed
Query public.user_private_details table for user email addresses instead of public.users table
Reference environment variables by name only in code - never echo values
Prefer interface over type for defining object shapes in TypeScript
Use camelCase for variable and function names in TypeScript/JavaScript
Use SCREAMING_SNAKE_CASE for constants

**/*.{ts,tsx}: Always prefer importing database types from packages/types/src/db.ts. Never attempt to run migrations yourself - only after user runs bun sb:push and bun sb:typegen.
The public.users table does NOT contain an email field. User email addresses are stored in public.user_private_details. When querying user email, always use user_private_details table, not users.

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript
N...

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/app/api/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/app/api/**/*.ts: Use Zod for runtime validation of external inputs at API boundaries
Validate inputs early in API routes and return 400/401/403 for bad requests
Never leak stack traces to client; wrap external service calls and surface sanitized error messages
Log detailed errors server-side only; never expose sensitive information to client
Define and export runtime if edge is required: export const runtime = 'edge'
Use @tuturuuu/supabase client for authentication in API routes
Use normalizeWorkspaceId() helper to resolve workspace ID parameters in API routes
Enforce auth on AI endpoints by getting user via createClient() and returning 401 if absent
Check feature flags in workspace_secrets before serving AI features
Use Vercel AI SDK streamObject/generateObject with selected model for AI operations
Set maxDuration for long-running AI operations
Never log secrets or raw provider responses in AI endpoints

apps/*/src/app/api/**/*.ts: Use createClient(request) pattern in API routes to support both Bearer token auth (mobile) and cookie auth (web)
Use export const runtime = 'edge' to explicitly target edge runtime execution in Next.js API routes when appropriate
Use Zod for runtime validation of external inputs (API bodies, env-derived config) at system boundaries
Resolve workspace ID parameters (wsId) to UUIDs before database operations; use normalizeWorkspaceId() for special identifiers like 'personal' and 'internal'
Validate inputs early in API routes; return 4xx errors before processing to fail fast
Add export const runtime = 'edge' for routes that should execute on the edge runtime
Use Vercel AI SDK generateObject / streamObject with Zod schema for deterministic AI output structure
Use createAdminClient() (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/**/*.{ts,tsx}: Extract functions used ≥2 times to src/lib/ or src/utils/
Extract complex state logic to custom hooks in src/hooks/
Extract pure computations as testable utilities

apps/*/src/**/*.{ts,tsx}: Never log secrets, API keys, tokens, or raw sensitive data to console; only reference environment variable names
Query the public.user_private_details table for user email addresses; the public.users table does NOT contain an email field

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

Always use bun type-check command for type checking. Do NOT use npx tsgo, bunx tsgo, or other alternatives.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.{ts,tsx,js,jsx,json,yaml,yml}

📄 CodeRabbit inference engine (GEMINI.md)

Use Biome for linting and formatting. Run bun format-and-lint:fix to automatically fix issues.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/{src,lib}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Files >400 LOC and components >200 LOC should be refactored into smaller, focused units. Apply best practices to ALL code, regardless of age. Follow single responsibility principle, extract utilities/hooks for complex logic, and leave code better than you found it.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

apps/web/**/*.{ts,tsx}: Never use useEffect for data fetching. MANDATORY: Use TanStack Query (useQuery, useMutation, useInfiniteQuery) for all client-side data fetching.
Never use raw fetch() without TanStack Query wrapper in client components. Always use TanStack Query hooks for client-side data fetching.
Any fetch() inside a queryFn MUST include cache: 'no-store' to prevent browser HTTP cache from serving stale responses after TanStack Query invalidation.
Use stable array query keys following pattern: [domain, subdomain?, paramsHash, version?]. Set staleTime > 0 for rarely-changing data. Implement optimistic updates with rollback. Use narrow invalidations, avoid global invalidateQueries().
Follow the Tailwind Dynamic Color Policy. Never use hard-coded color classes like text-blue-500. Instead, use the dynamic-* tokens, e.g., text-dynamic-blue.
Use sonner for toasts: import { toast } from '@tuturuuu/ui/sonner';. Avoid the deprecated @tuturuuu/ui/toast. Never use native browser dialogs like alert() or confirm().
Use the dialog system for modals: import { Dialog, ... } from '@tuturuuu/ui/dialog';. Never use native browser dialogs.
Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries. Use normalizeWorkspaceId(wsId) helper for API routes or accept workspace prop in components.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

When adding new pages or routes, ALWAYS update the main navigation file (apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx). Add routes to both the aliases array and children navigation items with proper icons, permission checks, and translation keys in both en.json AND vi.json.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Create new API routes in apps/<app>/src/app/api/.... Use Supabase client wrappers for authentication and Zod for input validation.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

The mobile app connects to apps/web API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use createClient(request) for Bearer token auth.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Extract utilities to src/lib/, hooks to src/hooks/, and sub-components as needed. Apply best practices to BOTH old and new code. Follow single responsibility principle, use meaningful names, and eliminate duplication.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Avoid using any type in TypeScript; narrow unknown at boundaries with explicit type guards
Use discriminated unions over enums unless runtime enum is required
Always use bun type-check for TypeScript type checking; do NOT use npx tsgo, bunx tsgo, or other alternatives

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/mobile/lib/**/*{cubit,bloc}.dart

📄 CodeRabbit inference engine (GEMINI.md)

If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
🧠 Learnings (22)
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/l10n/arb/*.arb : Maintain ARB localization files in `lib/l10n/arb/` with both English and Vietnamese translations

Applied to files:

  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/l10n/**/*.{arb,dart} : When updating `apps/mobile/lib/l10n/arb/*.arb`, regenerate or update `apps/mobile/lib/l10n/gen/*` so tracked outputs stay in sync.

Applied to files:

  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : The Flutter mobile app uses BLoC/Cubit state management, `go_router` navigation, and `supabase_flutter` for auth. Build flavors: `main_development.dart`, `main_staging.dart`, `main_production.dart`.

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/**/test/**/*.dart : Wrap widgets that use `shadcn_flutter` in `shad.ShadcnApp` with `shad.ShadcnLocalizations.delegate` so `shad.Theme.of(context)` resolves in tests.

Applied to files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `flutter_bloc` with Cubits for feature state management in Flutter app

Applied to files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widget tests rendering `shadcn_flutter` components with `shad.ShadcnApp` including `shad.ShadcnLocalizations.delegate`

Applied to files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widgets using `shadcn_flutter` with `shad.ShadcnApp` in tests to provide theme context

Applied to files:

  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the `@` check and any email-specific success note (use `TextInputType.emailAddress` or an explicit parameter).

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Capture `context` dependencies before first `await` and guard `BuildContext` usage after `await` with `if (!context.mounted) return;` in Flutter

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `supabase_flutter` with `flutter_secure_storage` for token persistence in Flutter app

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*{cubit,bloc}.dart : If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Resolve workspace ID parameters (`wsId`) to UUIDs before database operations; use `normalizeWorkspaceId()` for special identifiers like 'personal' and 'internal'

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `normalizeWorkspaceId()` helper to resolve workspace ID parameters in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Database `ws_id` columns ALWAYS store UUIDs. Route parameters (`wsId`) may contain special identifiers like `personal` or `internal`. NEVER use raw `wsId` directly in database queries. Use `normalizeWorkspaceId(wsId)` helper for API routes or accept `workspace` prop in components.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/**/src/components/**/*.{ts,tsx} : Use `workspace.id` directly from workspace object in client components instead of raw `wsId` parameter

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to packages/ui/src/**/*.{ts,tsx} : Extract portable UI components with no `@/` imports to `packages/ui`. Pass `workspace` prop to child components instead of raw `wsId`.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to scripts/**/*.{js,ts} : Use workspace-relative paths in `apply_patch` (e.g., `apps/web/...`) instead of absolute Windows paths for better cross-platform compatibility

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation in `apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx` when adding new routes - add to both `aliases` array and `children` navigation items with proper icons and permissions

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation file when adding new routes: add entries to both `aliases` array and `children` navigation items with proper icons and permission checks

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `createAdminClient()` (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `tuturuuu/supabase` client for authentication in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/*/src/app/api/**/*.{ts,tsx} : Create new API routes in `apps/<app>/src/app/api/...`. Use Supabase client wrappers for authentication and Zod for input validation.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
Comment thread apps/mobile/lib/features/time_tracker/widgets/history_tab.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 49.11%. Comparing base (54d8c88) to head (2a8ee02).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4341      +/-   ##
==========================================
+ Coverage   49.10%   49.11%   +0.01%     
==========================================
  Files         256      256              
  Lines       19633    19633              
  Branches     6713     6706       -7     
==========================================
+ Hits         9641     9643       +2     
+ Misses       8142     8141       -1     
+ Partials     1850     1849       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@VNOsST VNOsST marked this pull request as draft February 24, 2026 03:55
- Updated `getStats` method to accept nullable `userId` and conditionally include it in API requests.
- Refactored `loadHistoryInitial` to support an optional `throwOnError` parameter.
- Improved month navigation logic in `TimeTrackerCubit`.
- Enhanced error handling in `EditSessionDialog` and `DateTimePicker` widgets.
- Updated `HistoryPeriodControls` to dynamically adjust based on the first day of the week.
- Refined `HistoryTab` layout using `CustomScrollView` for better performance and organization.
- Adjusted styles in `StatCard` for consistency with typography updates.
- Added validation for session duration formatting in `HistoryStatsAccordion`.
- Introduced new API route and helper functions for session management in the web application.
@VNOsST VNOsST marked this pull request as ready for review February 24, 2026 04:21
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 11 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mobile/lib/features/time_tracker/widgets/history_tab.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/widgets/history_tab.dart:237">
P3: Localize the "Unknown date" label instead of hard-coding it so it can be translated like the rest of the history UI.</violation>
</file>

<file name="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts">

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts:363">
P2: The session "rollback" inside the `breakError` handler fires before the session has been modified (the `pause_session_for_break` RPC that actually updates the session comes after). The rollback is therefore an unnecessary DB write that may overwrite legitimate session state. It should be removed.</violation>

<violation number="2" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts:621">
P1: `duration_seconds` is not recalculated when only `endTime` is provided. If a caller sends just an `endTime` update, the `end_time` column is written to the DB but `duration_seconds` stays at its old value, leaving the two fields inconsistent. Fall back to `session.start_time` when `startTime` is absent.</violation>
</file>

<file name="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart:688">
P2: Month navigation can compute the wrong year when moving backwards across January because `~/` truncates toward zero for negative values. This will return December of the same year instead of the previous year.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/widgets/history_tab.dart Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)

36-61: ⚠️ Potential issue | 🟠 Major

GET handler uses raw wsId in database queries — normalize it like PATCH and DELETE.

The PATCH (line 88) and DELETE (line 209) handlers both call normalizeWorkspaceId(wsId, supabase) before querying, but GET uses the raw route parameter directly on lines 39 and 60. Route wsId may contain special identifiers like personal or internal that are not valid UUIDs for DB columns.

Proposed fix
   try {
     const { wsId, sessionId } = await params;
     const supabase = await createClient(request);
 
+    let normalizedWsId: string;
+    try {
+      normalizedWsId = await normalizeWorkspaceId(wsId, supabase);
+    } catch {
+      return NextResponse.json(
+        { error: 'Workspace not found' },
+        { status: 404 }
+      );
+    }
+
     const {
       data: { user },
       error: authError,
     } = await supabase.auth.getUser();
     if (authError || !user) {
       return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
     }
 
     const { data: memberCheck } = await supabase
       .from('workspace_members')
       .select('id:user_id')
-      .eq('ws_id', wsId)
+      .eq('ws_id', normalizedWsId)
       .eq('user_id', user.id)
       .single();
 
     // ...
 
     const { data, error } = await supabase
       .from('time_tracking_sessions')
       .select(/* ... */)
       .eq('id', sessionId)
-      .eq('ws_id', wsId)
+      .eq('ws_id', normalizedWsId)
       .eq('user_id', user.id)
       .single();

Based on learnings: "Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries. Use normalizeWorkspaceId(wsId) helper for API routes."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 36 - 61, The GET handler is using the raw route wsId in DB queries;
call the same helper used by PATCH/DELETE—normalizeWorkspaceId(wsId,
supabase)—at the start of the GET flow, await its result into a variable (e.g.,
normalizedWsId), and use that normalizedWsId in both the workspace_members
lookup (memberCheck) and the time_tracking_sessions query (instead of raw wsId);
ensure you handle any error or null return from normalizeWorkspaceId before
proceeding.
🤖 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/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart`:
- Around line 726-735: The week range calculation in _historyPeriodRange (case
HistoryViewMode.week) hardcodes Monday via localAnchor.weekday -
DateTime.monday, causing mismatch with the locale-aware label in
HistoryPeriodControls; update the calculation to compute the start offset using
the actual first day of week (use the same firstDayOfWeek/locale value used by
HistoryPeriodControls or pass it into _historyPeriodRange) and subtract
(localAnchor.weekday - firstDayOfWeek) modulo 7 to get the correct start, then
compute end as start + 7 days - 1 microsecond as before so the API range matches
the UI label.
- Around line 681-706: The month-branch in _moveHistoryAnchor wrongly computes
year using integer division; replace the manual targetYear/targetMonth math by
leveraging Dart's DateTime normalization: construct a DateTime using
(current.year, current.month + delta, current.day, ...) to get the normalized
year/month, then clamp the day to the last day of that normalized month if
current.day exceeds it; ensure this logic is used only for HistoryViewMode.month
in the switch so hour/minute/second/millisecond/microsecond are preserved.

In `@apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart`:
- Around line 255-271: The exception handler currently uses an unbound `on
Exception` which swallows the error; change it to `on Exception catch (e, st)`
to bind the exception (and stacktrace) so you can log it before showing the
toast and resetting `_isSubmitting`; keep the `if (!context.mounted) return;`
check and call your logger (or `debugPrint`) with `e`/`st` inside the `catch`
block prior to invoking `shad.showToast` and `setState(() => _isSubmitting =
false);` so failures in the EditSessionDialog flow are observable.
- Around line 288-293: The current validation blocks Save when times are
inverted even if times are read-only; update the _isValid getter to only enforce
endTime > startTime when canEditTimes is true (e.g. return true if !canEditTimes
|| _endTime.isAfter(_startTime)) so metadata-only edits can be saved, and
replace the hardcoded 'Invalid' in _formatDuration with the localized string
from l10n (e.g. use context.l10n.invalid or the project's appropriate l10n key)
so localization is respected.

In `@apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart`:
- Around line 124-148: The week-start logic in the widget (_periodRange) uses a
locale-specific firstDayOfWeek but the cubit's _historyPeriodRange always uses
DateTime.monday, causing mismatched ranges; update the cubit by adding a
firstDayOfWeek parameter to TimeTrackerCubit._historyPeriodRange (and its
callers) and use the same calculation as the widget: compute localAnchor from
the anchor date, compute offset = (localAnchor.weekday - firstDayOfWeek + 7) %
7, set start = localAnchor.subtract(Duration(days: offset)) and end =
start.add(Duration(days: 6)) so the API queries use the identical week range the
UI displays.

In `@apps/mobile/lib/features/time_tracker/widgets/history_tab.dart`:
- Around line 43-52: _handleScroll can run after the State is disposed and
currently reads context inside the scroll callback; add a mounted guard at the
top of _handleScroll (e.g., return early if !mounted or !context.mounted) before
accessing context.read<WorkspaceCubit>() and context.read<TimeTrackerCubit>();
ensure you capture any ids (via _currentUserId()) only after the mount check and
then call loadHistoryMore(wsId, userId) so the listener will not use context
when the widget is unmounted.
- Around line 234-252: The hardcoded 'Unknown date' literal used when building
_DayGroup labels should be localized; update the _groupByDay implementation to
accept a L10n reference (or access context.l10n) and replace both occurrences of
'Unknown date' with the localized string (e.g., context.l10n.unknownDate),
ensuring _DayGroup construction uses the localized label; adjust the caller(s)
of _groupByDay to pass the l10n instance if you choose the parameter approach
and keep dateFmt and _DayGroup usage unchanged.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart`:
- Around line 21-25: The calculation of cardWidth in the LayoutBuilder (builder)
uses constraints.maxWidth directly which can be unbounded or smaller than
spacing, producing double.infinity or negative widths; fix it by first checking
constraints.hasBoundedWidth (or constraints.maxWidth.isFinite) and computing an
availableWidth = max(0.0, constraints.maxWidth - spacing) with a sensible
fallback when unbounded (e.g., a default total width), then derive cardWidth =
availableWidth / 2 and clamp it to a minimum card width (e.g., via math.max) to
ensure cardWidth is finite and non-negative before passing it to Wrap/SizedBox.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/helpers.ts:
- Around line 695-704: The current error handling in helpers.ts relies on
fragile string matching of error.message (checks for 'older than' and 'must be
submitted as requests') before returning a 400; change this to rely on
structured error metadata instead: remove the message.includes(...) checks and
only treat errors with error.code === 'P0001' (or a new custom error
property/hint you add in the database function) as client 400 responses,
returning NextResponse.json({ error: error.message }, { status: 400 });
otherwise rethrow the error; update the producer (the DB function that can throw
these conditions) to attach a stable error.code or hint if necessary so the
handler (the block that currently does the message.includes checks) can make a
robust decision.
- Around line 62-75: Replace the exported type alias SessionRecord with an
exported interface named SessionRecord that preserves the same property names
and types (id, ws_id, user_id, title, description, category_id, task_id,
start_time, end_time, duration_seconds, is_running, pending_approval) so code
consuming SessionRecord remains unchanged; update the declaration syntax from
"export type SessionRecord = { ... }" to "export interface SessionRecord { ...
}".
- Around line 1-6: This file is too large; split its responsibilities into
smaller modules: extract all Zod schemas and exported types into a new
schemas.ts (move lines containing schema definitions and type exports), move
getSessionChainRoot and checkSessionThreshold into threshold.ts (preserve their
implementations and imports), and create an actions/ folder with separate files
for each handler (stop.ts, pause.ts, resume.ts, edit.ts) exporting the
corresponding action functions; update imports in the original helpers.ts to
re-export or delegate to these new modules so existing callers (e.g., functions
referencing getSessionChainRoot, checkSessionThreshold, and the action handlers)
keep working.
- Around line 525-549: The validation currently checks startTime and endTime for
being in the future but doesn't ensure startTime < endTime, which allows
negative durations; after the existing future-time checks in helpers.ts, add a
check that when both startTime and endTime are provided (and parseable to Dates)
the computed start date is strictly before the end date, and if not return a
NextResponse.json error (400) with a clear message like "startTime must be
before endTime" so duration_seconds cannot be negative; reference startTime,
endTime, new Date(...), and NextResponse.json when locating where to insert this
validation.
- Around line 77-95: Change getSessionChainRoot and checkSessionThreshold to
accept an existing sbAdmin client instead of calling createAdminClient() inside
them: update their signatures (e.g., getSessionChainRoot(sbAdmin, sessionId) and
checkSessionThreshold(sbAdmin, ...)) remove the internal createAdminClient()
calls and use the passed sbAdmin for the RPCs, and update all callers
(handleStopAction, handlePauseAction) to pass the sbAdmin they already have into
these helper functions so a single admin client is reused per request.
- Around line 350-388: The current flow inserts into time_tracking_breaks before
calling sbAdmin.rpc('pause_session_for_break'), which leads to orphaned break
rows if the RPC fails and the current "revert" update (the block updating
time_tracking_sessions to is_running: true, end_time: null) is a no-op because
the session hasn't been paused yet; reorder and error-handle to fix this: call
sbAdmin.rpc('pause_session_for_break', { p_session_id: sessionId, p_end_time:
endTime, p_duration_seconds: durationSeconds, p_pending_approval:
pendingApproval || false }) first and only insert into time_tracking_breaks
(using finalBreakTypeId/finalBreakTypeName, break_start: endTime, created_by:
userId) after the RPC succeeds; alternatively, if you must keep the current
order, ensure you delete the break row (DELETE from time_tracking_breaks where
session_id = sessionId and break_start = endTime and created_by = userId) when
the RPC returns an error and remove the pointless revert update block that
assumes the session was already paused.
- Around line 570-629: When session.is_running is true the current code silently
skips startTime/endTime handling; change this to return a 400 response when a
client supplies startTime or endTime for a running session (unless a bypass like
canBypass applies). In helpers.ts locate the block that checks
session.is_running (and uses updateData) and before skipping, add a guard that
if (session.is_running && (startTime !== undefined || endTime !== undefined) &&
!canBypass) then return NextResponse.json({ error: 'Cannot edit start_time or
end_time for a running session' }, { status: 400 }). This ensures clients
receive explicit feedback instead of having edits discarded.
- Around line 618-628: The current block only recalculates
updateData.duration_seconds when both startTime and endTime are provided; change
it to recompute duration whenever at least one of startTime or endTime is being
updated by using the existing session's stored start/end as fallbacks: derive
start = startTime ? new Date(startTime) : new Date(existingSession.start_time)
and end = endTime ? new Date(endTime) : new Date(existingSession.end_time), then
if both start and end are present compute updateData.duration_seconds =
Math.floor((end.getTime() - start.getTime())/1000); ensure you reference
updateData, startTime, endTime, duration_seconds and the existing session values
(e.g., existingSession.start_time / existingSession.end_time) so partial edits
update duration correctly.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 152-189: The switch over body.action (cases:
'stop','pause','resume','edit') can fall through if a new action is added;
update the switch in the route handler to be exhaustive by adding a default case
(or a post-switch throw/return) that returns a proper HTTP response or throws an
error when action is unrecognized; locate the switch that calls
handleStopAction, handlePauseAction, handleResumeAction, and handleEditAction
and add a fallback that logs the unknown action and returns a 400/500 response
(or throws) so the handler never silently returns undefined.

---

Outside diff comments:
In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 36-61: The GET handler is using the raw route wsId in DB queries;
call the same helper used by PATCH/DELETE—normalizeWorkspaceId(wsId,
supabase)—at the start of the GET flow, await its result into a variable (e.g.,
normalizedWsId), and use that normalizedWsId in both the workspace_members
lookup (memberCheck) and the time_tracking_sessions query (instead of raw wsId);
ensure you handle any error or null return from normalizeWorkspaceId before
proceeding.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76bb803 and 7c9b8cc.

📒 Files selected for processing (11)
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Comment on PR
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Build Android APK (Development)
  • GitHub Check: Build iOS (Development, simulator)
  • GitHub Check: build / build
  • GitHub Check: Run tests and collect coverage
  • GitHub Check: Type Check
  • GitHub Check: Verify generated types
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Playwright E2E
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (17)
apps/mobile/lib/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

apps/mobile/lib/**/*.dart: Use flutter_bloc with Cubits for feature state management in Flutter app
Use go_router with auth-aware redirects and ShellRoute for bottom tabs in Flutter app
Use supabase_flutter with flutter_secure_storage for token persistence in Flutter app
Follow very_good_analysis linting rules in Flutter app
Use on Exception catch (e) instead of bare catch in Flutter code; avoid catching Error subclasses
Capture context dependencies before first await and guard BuildContext usage after await with if (!context.mounted) return; in Flutter
Never return from a finally block in Dart/Flutter code
Use Future<void> Function() callbacks for mutation-driven UI actions in Flutter; await before closing dialogs

apps/mobile/lib/**/*.dart: When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the @ check and any email-specific success note (use TextInputType.emailAddress or an explicit parameter).
Use on Exception catch (e) (or specific exception types) instead of bare catch, avoid catching Error subclasses like TypeError, capture context dependencies before the first await, guard BuildContext usage after await with if (!context.mounted) return;, and do not return inside finally blocks.
The Flutter mobile app uses BLoC/Cubit state management, go_router navigation, and supabase_flutter for auth. Build flavors: main_development.dart, main_staging.dart, main_production.dart.
For mutation-driven Flutter UI actions (approve/reject/update), use Future<void> Function() callbacks (not VoidCallback), await them before closing dialogs/sheets, and surface failures in the UI (e.g., SnackBar).

apps/mobile/lib/**/*.dart: Use on Exception catch (e) with specific exception types in Flutter; avoid untyped catch blocks
Check context.mounted before using BuildContext after await in Flutter to preven...

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
apps/mobile/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Always run bun check:mobile after changes to apps/mobile/. This runs dart format --set-exit-if-changed lib test && flutter analyze && flutter test. All three checks MUST pass. If it reports a Dart format failure, rerun to confirm a clean pass.

apps/mobile/**/*.dart: Run dart format --set-exit-if-changed lib test in apps/mobile/ to verify code formatting; fix with dart format lib test
Run bun check:mobile (dart format + flutter analyze + flutter test) after making changes to apps/mobile/

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
apps/mobile/test/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Wrap Flutter widget tests rendering shadcn_flutter components with shad.ShadcnApp including shad.ShadcnLocalizations.delegate

Wrap Flutter widgets using shadcn_flutter with shad.ShadcnApp in tests to provide theme context

Files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
apps/mobile/**/test/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Wrap widgets that use shadcn_flutter in shad.ShadcnApp with shad.ShadcnLocalizations.delegate so shad.Theme.of(context) resolves in tests.

Files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript with explicit return types for exported functions
Prefer interface for defining object shapes in TypeScript
Use TanStack Query (React Query) for ALL client-side data fetching - NEVER use useEffect for data fetching
Always include cache: 'no-store' in fetch() calls within TanStack Query queryFn to prevent dual-layer caching conflicts
Use Server Components by default; add 'use client' only when necessary
Use @tuturuuu/ui/sonner for toast notifications instead of the deprecated @tuturuuu/ui/toast
Refactor files exceeding 400 LOC and components exceeding 200 LOC into smaller, focused units
Extract complex logic to separate utilities and custom hooks following single responsibility principle
Import database types from packages/types/src/db.ts instead of manually defining database types (only after user runs migrations via bun sb:push and typegen via bun sb:typegen)
Always use stable array query keys with structure: [domain, subdomain?, paramsHash, version?] in TanStack Query
Use discriminated unions over enums for TypeScript type definitions
Narrow unknown/any at boundaries; justify with comments if needed
Query public.user_private_details table for user email addresses instead of public.users table
Reference environment variables by name only in code - never echo values
Prefer interface over type for defining object shapes in TypeScript
Use camelCase for variable and function names in TypeScript/JavaScript
Use SCREAMING_SNAKE_CASE for constants

**/*.{ts,tsx}: Always prefer importing database types from packages/types/src/db.ts. Never attempt to run migrations yourself - only after user runs bun sb:push and bun sb:typegen.
The public.users table does NOT contain an email field. User email addresses are stored in public.user_private_details. When querying user email, always use user_private_details table, not users.

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript
N...

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/*/src/app/api/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/app/api/**/*.ts: Use Zod for runtime validation of external inputs at API boundaries
Validate inputs early in API routes and return 400/401/403 for bad requests
Never leak stack traces to client; wrap external service calls and surface sanitized error messages
Log detailed errors server-side only; never expose sensitive information to client
Define and export runtime if edge is required: export const runtime = 'edge'
Use @tuturuuu/supabase client for authentication in API routes
Use normalizeWorkspaceId() helper to resolve workspace ID parameters in API routes
Enforce auth on AI endpoints by getting user via createClient() and returning 401 if absent
Check feature flags in workspace_secrets before serving AI features
Use Vercel AI SDK streamObject/generateObject with selected model for AI operations
Set maxDuration for long-running AI operations
Never log secrets or raw provider responses in AI endpoints

apps/*/src/app/api/**/*.ts: Use createClient(request) pattern in API routes to support both Bearer token auth (mobile) and cookie auth (web)
Use export const runtime = 'edge' to explicitly target edge runtime execution in Next.js API routes when appropriate
Use Zod for runtime validation of external inputs (API bodies, env-derived config) at system boundaries
Resolve workspace ID parameters (wsId) to UUIDs before database operations; use normalizeWorkspaceId() for special identifiers like 'personal' and 'internal'
Validate inputs early in API routes; return 4xx errors before processing to fail fast
Add export const runtime = 'edge' for routes that should execute on the edge runtime
Use Vercel AI SDK generateObject / streamObject with Zod schema for deterministic AI output structure
Use createAdminClient() (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/**/*.{ts,tsx}: Extract functions used ≥2 times to src/lib/ or src/utils/
Extract complex state logic to custom hooks in src/hooks/
Extract pure computations as testable utilities

apps/*/src/**/*.{ts,tsx}: Never log secrets, API keys, tokens, or raw sensitive data to console; only reference environment variable names
Query the public.user_private_details table for user email addresses; the public.users table does NOT contain an email field

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

Always use bun type-check command for type checking. Do NOT use npx tsgo, bunx tsgo, or other alternatives.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
**/*.{ts,tsx,js,jsx,json,yaml,yml}

📄 CodeRabbit inference engine (GEMINI.md)

Use Biome for linting and formatting. Run bun format-and-lint:fix to automatically fix issues.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
**/{src,lib}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Files >400 LOC and components >200 LOC should be refactored into smaller, focused units. Apply best practices to ALL code, regardless of age. Follow single responsibility principle, extract utilities/hooks for complex logic, and leave code better than you found it.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

apps/web/**/*.{ts,tsx}: Never use useEffect for data fetching. MANDATORY: Use TanStack Query (useQuery, useMutation, useInfiniteQuery) for all client-side data fetching.
Never use raw fetch() without TanStack Query wrapper in client components. Always use TanStack Query hooks for client-side data fetching.
Any fetch() inside a queryFn MUST include cache: 'no-store' to prevent browser HTTP cache from serving stale responses after TanStack Query invalidation.
Use stable array query keys following pattern: [domain, subdomain?, paramsHash, version?]. Set staleTime > 0 for rarely-changing data. Implement optimistic updates with rollback. Use narrow invalidations, avoid global invalidateQueries().
Follow the Tailwind Dynamic Color Policy. Never use hard-coded color classes like text-blue-500. Instead, use the dynamic-* tokens, e.g., text-dynamic-blue.
Use sonner for toasts: import { toast } from '@tuturuuu/ui/sonner';. Avoid the deprecated @tuturuuu/ui/toast. Never use native browser dialogs like alert() or confirm().
Use the dialog system for modals: import { Dialog, ... } from '@tuturuuu/ui/dialog';. Never use native browser dialogs.
Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries. Use normalizeWorkspaceId(wsId) helper for API routes or accept workspace prop in components.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

When adding new pages or routes, ALWAYS update the main navigation file (apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx). Add routes to both the aliases array and children navigation items with proper icons, permission checks, and translation keys in both en.json AND vi.json.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/*/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Create new API routes in apps/<app>/src/app/api/.... Use Supabase client wrappers for authentication and Zod for input validation.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/web/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

The mobile app connects to apps/web API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use createClient(request) for Bearer token auth.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Extract utilities to src/lib/, hooks to src/hooks/, and sub-components as needed. Apply best practices to BOTH old and new code. Follow single responsibility principle, use meaningful names, and eliminate duplication.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Avoid using any type in TypeScript; narrow unknown at boundaries with explicit type guards
Use discriminated unions over enums unless runtime enum is required
Always use bun type-check for TypeScript type checking; do NOT use npx tsgo, bunx tsgo, or other alternatives

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
apps/mobile/lib/**/*{cubit,bloc}.dart

📄 CodeRabbit inference engine (GEMINI.md)

If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
🧠 Learnings (28)
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to **/*.tsx : Ensure interactive elements are keyboard-accessible with natural tab order; provide `aria-label` or text content for icon-only buttons

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/**/test/**/*.dart : Wrap widgets that use `shadcn_flutter` in `shad.ShadcnApp` with `shad.ShadcnLocalizations.delegate` so `shad.Theme.of(context)` resolves in tests.

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : The Flutter mobile app uses BLoC/Cubit state management, `go_router` navigation, and `supabase_flutter` for auth. Build flavors: `main_development.dart`, `main_staging.dart`, `main_production.dart`.

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `flutter_bloc` with Cubits for feature state management in Flutter app

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widgets using `shadcn_flutter` with `shad.ShadcnApp` in tests to provide theme context

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widget tests rendering `shadcn_flutter` components with `shad.ShadcnApp` including `shad.ShadcnLocalizations.delegate`

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `supabase_flutter` with `flutter_secure_storage` for token persistence in Flutter app

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Capture `context` dependencies before first `await` and guard `BuildContext` usage after `await` with `if (!context.mounted) return;` in Flutter

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*{cubit,bloc}.dart : If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Applied to files:

  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/**/*.dart : Run `dart format --set-exit-if-changed lib test` in `apps/mobile/` to verify code formatting; fix with `dart format lib test`

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to **/*.{tsx,css} : Avoid hard-coded palette utility classes (e.g., `text-blue-500`, `bg-purple-300/10`); use Tailwind dynamic color tokens instead (e.g., `text-dynamic-blue`, `bg-dynamic-purple/10`)

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `on Exception catch (e)` (or specific exception types) instead of bare `catch`, avoid catching `Error` subclasses like `TypeError`, capture `context` dependencies before the first `await`, guard `BuildContext` usage after `await` with `if (!context.mounted) return;`, and do not `return` inside `finally` blocks.

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Check `context.mounted` before using `BuildContext` after `await` in Flutter to prevent errors

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `on Exception catch (e)` instead of bare `catch` in Flutter code; avoid catching `Error` subclasses

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : For mutation-driven Flutter UI actions (approve/reject/update), use `Future<void> Function()` callbacks (not `VoidCallback`), await them before closing dialogs/sheets, and surface failures in the UI (e.g., `SnackBar`).

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the `@` check and any email-specific success note (use `TextInputType.emailAddress` or an explicit parameter).

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `on Exception catch (e)` with specific exception types in Flutter; avoid untyped `catch` blocks

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `Future<void> Function()` callbacks for mutation-driven UI actions in Flutter; await before closing dialogs

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Capture dependencies from `context` (e.g., `context.read<T>()`) BEFORE the first `await` in Flutter async methods

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `Future<void> Function()` callbacks for widget actions depending on server mutations; await them and only close UI on success

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `normalizeWorkspaceId()` helper to resolve workspace ID parameters in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Resolve workspace ID parameters (`wsId`) to UUIDs before database operations; use `normalizeWorkspaceId()` for special identifiers like 'personal' and 'internal'

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/**/src/components/**/*.{ts,tsx} : Use `workspace.id` directly from workspace object in client components instead of raw `wsId` parameter

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Database `ws_id` columns ALWAYS store UUIDs. Route parameters (`wsId`) may contain special identifiers like `personal` or `internal`. NEVER use raw `wsId` directly in database queries. Use `normalizeWorkspaceId(wsId)` helper for API routes or accept `workspace` prop in components.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to packages/ui/src/**/*.{ts,tsx} : Extract portable UI components with no `@/` imports to `packages/ui`. Pass `workspace` prop to child components instead of raw `wsId`.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation file when adding new routes: add entries to both `aliases` array and `children` navigation items with proper icons and permission checks

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to scripts/**/*.{js,ts} : Use workspace-relative paths in `apply_patch` (e.g., `apps/web/...`) instead of absolute Windows paths for better cross-platform compatibility

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `tuturuuu/supabase` client for authentication in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
🔇 Additional comments (17)
apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart (1)

104-116: Typography hierarchy update looks good.

Value/label styling is clearer and consistent with other stat cards.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)

199-267: LGTM!

DELETE handler correctly normalizes the workspace ID, verifies ownership before deletion, and uses the admin client for the actual delete.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts (2)

173-281: LGTM on handleStopAction.

The threshold check, active-break closure, and branching between already-paused vs. running sessions are handled correctly with clear control flow.


428-504: LGTM on handleResumeAction.

Correctly closes the active break, creates a chained session with parent_session_id, and returns break duration metadata.

apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart (3)

243-258: Previous context.mounted and error.toString() issues are resolved — LGTM.

Both if (!context.mounted) return; guards (Lines 243 and 256) and the localized error message (Line 264) correctly address the concerns raised in the prior review.


331-379: context.mounted guards after async pickers are in place — LGTM.

Both showDatePicker (Line 339) and showTimePicker (Line 364) are correctly guarded with if (!context.mounted) return; before acting on their results.


66-70: No issues found. The exceedsThreshold function properly handles nullable DateTime with an explicit null guard that returns false, allowing time editing when startTime is null.

apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart (1)

13-65: LGTM — new history state fields are well-structured.

The sentinel pattern for historyAnchorDate and historyNextCursor, the clear* flags for historyPeriodStats and historyNextCursor, and the Equatable props coverage all look correct and consistent with the existing patterns in this state class.

apps/mobile/lib/data/repositories/time_tracker_repository.dart (2)

248-268: LGTM — getHistorySessions implementation is clean.

The cursor-based pagination with conditional query parameters follows the existing repository patterns well.


487-502: LGTM — getPeriodStats endpoint integration looks good.

apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart (1)

85-196: Good test coverage for the new history behavior.

The precondition assertion at line 128 addresses the previously noted concern about test fragility. The pagination test using cursor-based mock differentiation is a clean approach.

apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart (1)

164-221: Good improvement: _SegmentTab now has proper accessibility semantics.

The use of Semantics(button: true) with InkWell provides keyboard focus, tap feedback, and semantic labeling.

apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart (1)

1-77: Clean accordion implementation with proper state handling.

The loading/null/data branching in _Body, the AnimatedCrossFade for expand/collapse, and the _formatSeconds fix for sub-minute durations all look good.

apps/mobile/lib/features/time_tracker/widgets/history_tab.dart (1)

75-211: Good migration to CustomScrollView with SliverList.builder for lazy rendering.

The sliver-based layout addresses the previously noted performance concern about eager widget construction. The paginated loading, "load more" button, and end-of-list footer are well structured.

apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart (3)

302-340: Good use of Dart 3 record .wait and throwOnError pattern.

The parallel fetch with type-safe destructuring and the conditional rethrow address both prior review comments. Clean implementation.


26-111: loadData fires all futures first then awaits — good parallel execution.

However, note that _repo.getActiveBreak (line 67) is still sequential since it depends on runningSession. This is unavoidable and correctly handled.


343-377: loadHistoryMore correctly guards against concurrent loads.

The combined check for historyHasMore, historyNextCursor, isHistoryLoadingMore, and isHistoryLoading prevents redundant requests from scroll events.

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Comment thread apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart Outdated
@VNOsST VNOsST changed the title feat: time tracker history page feat(mobile): time tracker history page Feb 24, 2026
…ation updates

- Added support for customizable first day of the week in time tracking features.
- Updated `loadData`, `goToPreviousPeriod`, `goToNextPeriod`, and related methods to accept `firstDayOfWeek` parameter.
- Refactored `TimeTrackerPage` and `HistoryTab` to utilize the new first day of the week logic.
- Improved localization for invalid duration and unknown date messages in the edit session dialog.
- Introduced new API schemas and actions for session management, including edit and pause functionalities.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 18 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts">

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts:38">
P2: Swallowing the break update error means the API can resume a new session while the prior break stays open, leaving inconsistent time-tracking data. Consider failing the request when closing the break fails.</violation>
</file>

<file name="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart:256">
P2: Defaulting firstDayOfWeek to Monday here overwrites the previously selected week start whenever this method is called without an explicit value, so locales using Sunday (or other starts) will silently revert to Monday. Use a nullable parameter and fall back to _historyFirstDayOfWeek instead of resetting it.</violation>
</file>

<file name="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts">

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts:52">
P2: Validate start/end ordering when only one time field is updated, using the existing session times, or you can end up with start >= end and negative durations.</violation>
</file>

<file name="apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart:55">
P3: The same `_firstDayOfWeek` implementation is duplicated in three widgets. This is maintainability debt; consider extracting a shared helper (e.g., top-level function or extension) and reusing it to avoid divergent fixes.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
Comment thread apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/mobile/lib/l10n/arb/app_en.arb (1)

167-272: ⚠️ Potential issue | 🟠 Major

Regenerate l10n outputs for ARB changes.

These new keys require updating apps/mobile/lib/l10n/gen/* to keep generated localizations in sync; the generated files aren’t shown in this PR snippet. Please regenerate and include them.

As per coding guidelines, "When updating apps/mobile/lib/l10n/arb/*.arb, regenerate or update apps/mobile/lib/l10n/gen/* so tracked outputs stay in sync."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/l10n/arb/app_en.arb` around lines 167 - 272, The ARB
additions in apps/mobile/lib/l10n/arb/app_en.arb were not propagated to the
generated localization outputs in apps/mobile/lib/l10n/gen/*; regenerate the
l10n outputs (e.g., run the project's localization generation command such as
the Flutter gen-l10n or intl_utils generate task used by this repo) so that the
new keys from app_en.arb appear in the generated files, then add the updated
files under apps/mobile/lib/l10n/gen/ to this PR; ensure the generation covers
the new placeholders like timerRequestActivityPageInfo and
timerAllEditsRequireApproval.
apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart (1)

27-118: ⚠️ Potential issue | 🟠 Major

Rethrow repository errors after emitting error state.

loadData and loadHistoryMore catch and swallow exceptions, preventing callers from reacting to failures. Consider a throwOnError flag (like loadHistoryInitial) or always rethrow after emitting the error state.

💡 Example fix (conditional rethrow)
-Future<void> loadData(
+Future<void> loadData(
   String wsId,
   String userId, {
     int? firstDayOfWeek,
+    bool throwOnError = false,
   }) async {
   ...
-  } on Exception catch (e) {
+  } on Exception catch (e) {
     emit(
       state.copyWith(status: TimeTrackerStatus.error, error: e.toString()),
     );
+    if (throwOnError) rethrow;
   }
 }
-Future<void> loadHistoryMore(
+Future<void> loadHistoryMore(
   String wsId,
   String userId, {
     int? firstDayOfWeek,
+    bool throwOnError = false,
   }) async {
   ...
-  } on Exception catch (e) {
-    emit(state.copyWith(isHistoryLoadingMore: false, error: e.toString()));
-  }
+  } on Exception catch (e) {
+    emit(state.copyWith(isHistoryLoadingMore: false, error: e.toString()));
+    if (throwOnError) rethrow;
+  }
 }
As per coding guidelines, "If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally."

Also applies to: 400-444

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart` around
lines 27 - 118, The catch block in loadData (and similarly in loadHistoryMore)
swallows repository exceptions after emitting an error state; update these
methods (loadData and loadHistoryMore) to rethrow the caught exception after
calling emit (or add a throwOnError boolean parameter like loadHistoryInitial
and conditionally rethrow) so callers can react to failures; ensure you
reference the existing emit(state.copyWith(..., status: TimeTrackerStatus.error,
error: e.toString())) call and then rethrow the Exception (or only rethrow when
throwOnError is true) to preserve the current state emission behavior.
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (3)

271-276: 🧹 Nitpick | 🔵 Trivial

Defense-in-depth: scope the DELETE query with ws_id.

The ownership check at lines 259–265 already ensures the session belongs to the user and workspace, so this is safe in practice. However, adding ws_id to the delete filter provides an extra guard against TOCTOU races or future refactors that might weaken the preceding check.

Proposed fix
     const { error } = await sbAdmin
       .from('time_tracking_sessions')
       .delete()
-      .eq('id', sessionId);
+      .eq('id', sessionId)
+      .eq('ws_id', normalizedWsId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 271 - 276, The DELETE query currently removed a session by id only;
add a defensive filter for workspace id to prevent TOCTOU or refactor
regressions by adding an equality filter for ws_id on the same query. Locate the
call chain created by createAdminClient() where
sbAdmin.from('time_tracking_sessions').delete().eq('id', sessionId) is used and
append an additional .eq('ws_id', wsId) to the delete query so the deletion is
scoped to the current workspace.

24-63: 🧹 Nitpick | 🔵 Trivial

Consider extracting the repeated auth+normalization boilerplate.

The pattern of normalizeWorkspaceIdgetUserworkspace_members check is duplicated across all three handlers (~30 lines each). A shared helper like authenticateAndResolveWorkspace(request, wsId) returning { user, normalizedWsId, supabase } or an error response would reduce duplication and ensure consistency. The file is within LOC limits, so this is optional.

Also applies to: 97-131, 223-257

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 24 - 63, Extract the repeated auth+workspace normalization logic
into a helper function (e.g., authenticateAndResolveWorkspace) that accepts
(request, wsId) and returns a successful result object { supabase, user,
normalizedWsId } or a NextResponse-like error; move the current sequence that
calls createClient(request), normalizeWorkspaceId(wsId, supabase),
supabase.auth.getUser(), and the workspace_members check into that helper, and
update the three handlers to call authenticateAndResolveWorkspace(...) and
early-return the error response if provided, otherwise use the returned
supabase, user, and normalizedWsId for the rest of the handler logic.

219-287: ⚠️ Potential issue | 🟠 Major

ADD threshold/permission checks to DELETE handler to match PATCH enforcement.

The DELETE handler bypasses the bypass_time_tracking_request_approval permission and threshold checks that PATCH enforces. When PATCH prevents editing sessions older than the workspace's missed_entry_date_threshold, users can still delete those same sessions. If thresholds are meant to enforce an approval workflow for time record changes, deletion should be gated the same way.

Consider adding threshold validation (via checkSessionThreshold()) and permission checks to prevent deletion of sessions that exceed the threshold, unless the user has bypass_time_tracking_request_approval.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 219 - 287, The DELETE handler currently removes a session without
enforcing the same threshold/permission checks as PATCH; update the DELETE
function to call the same threshold validator (e.g.,
checkSessionThreshold(sessionId, normalizedWsId, sbAdmin) or equivalent) after
resolving normalizedWsId and before performing the admin delete, and also verify
the user’s bypass permission (bypass_time_tracking_request_approval) on their
workspace membership (same place you check memberCheck) so that if the session
exceeds the workspace's missed_entry_date_threshold you return a 403 (or the
same error/response PATCH uses) unless the member has the bypass permission;
keep using the existing symbols (DELETE, normalizeWorkspaceId,
createAdminClient, checkSessionThreshold, bypass_time_tracking_request_approval)
and throw/return consistent errors when checks fail before calling
sbAdmin.from('time_tracking_sessions').delete().
♻️ Duplicate comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)

167-209: Default case in switch — past concern addressed.

The default branch now returns a 400 for unsupported actions, which prevents the handler from silently returning undefined. This addresses the previous review feedback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 167 - 209, The switch on body.action can fall through and return
undefined for unsupported actions; ensure the default branch returns a 400 JSON
response using NextResponse.json({ error: `Unsupported action` }, { status: 400
}) in
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
(the switch that calls handleStopAction, handlePauseAction, handleResumeAction,
handleEditAction) and confirm NextResponse is imported so callers never receive
undefined.
🤖 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/mobile/lib/features/time_tracker/view/time_tracker_page.dart`:
- Around line 55-69: The _firstDayOfWeek helper is duplicated across three
classes; extract it into a single shared private top-level function (or a small
extension) so all callers reuse the same implementation: move the weekdayByIndex
array and logic that uses MaterialLocalizations.of(context).firstDayOfWeekIndex
into one private function (e.g., _firstDayOfWeek(BuildContext)), replace the
three in-file copies with calls to that single function, and delete the
duplicate definitions; ensure all references (call sites) still import the file
or scope where the new shared helper lives.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart`:
- Around line 23-34: The cardWidth calculation can exceed the parent when
baseWidth < minCardWidth; update the computation in the LayoutBuilder so
cardWidth is clamped to not exceed baseWidth (e.g. compute cardWidth from
availableWidth/2 then apply math.min(baseWidth, ...) or clamp between
minCardWidth and baseWidth), referencing the existing variables cardWidth,
baseWidth, minCardWidth and availableWidth so the SizedBox(width: cardWidth)
never causes horizontal overflow.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts:
- Around line 87-104: The break insert can fail after pause_session_for_break
updated the session, leaving it paused with no break record; before returning
the 500 response you must roll back the session update: using sbAdmin and
sessionId, call the update on time_tracking_sessions to restore paused=false
(and reset paused_at, pause_expires_at and any status fields changed by
pause_session_for_break) and set updated_by to userId, log the rollback result,
then return the error response; alternatively wrap both session update and break
insert in a server-side transaction or RPC to make them atomic, but if keeping
the current flow ensure the explicit rollback happens on breakError.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts:
- Around line 30-40: The current resume flow updates the activeBreak row via
sbAdmin.from('time_tracking_breaks').update(...) and logs updateError but
continues to create a new running session, which may produce overlapping
periods; modify the resume handler so that if updateError is truthy (the update
that closes the break failed) you abort the resume path immediately (return an
error response or throw) instead of proceeding to create the session, or move
both operations into a single backend transaction/RPC so both succeed or fail
together; check the update result around activeBreak/ updateError/ resumeTime
and ensure session creation is only executed when the break-close succeeded.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts:
- Around line 61-71: The code currently logs but continues when closing an
active break fails (see activeBreak handling using
sbAdmin.from('time_tracking_breaks').update(...) which yields updateError),
leaving an open break while the session is stopped; change the stop flow to
abort on break-close failure by returning or throwing when updateError is
present (or use a single transactional/RPC endpoint to close the break and stop
the session atomically), ensuring the stop handler (the surrounding function
that calls this update and later stops the session) does not proceed if the
update to break_end (endTime) fails.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 159-161: The error response uses Response.json(...) which is
inconsistent with the rest of the file; update the permission-check return to
use NextResponse.json(...) instead (i.e., replace Response.json with
NextResponse.json in the block that checks the permissions variable) so it
matches other error responses and existing imports (ensure NextResponse is
already referenced in this module).

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/schemas.ts:
- Around line 36-44: The editActionSchema currently allows any string for
startTime and endTime; change those validators to z.string().datetime() (keeping
.optional() if still optional) so invalid datetime strings fail at the schema
boundary — update the editActionSchema's startTime and endTime entries to use
z.string().datetime().optional() (or z.string().datetime().nullable().optional()
if you need to allow explicit null) to enforce ISO/UTC/offset datetime formats
supported by Zod.

---

Outside diff comments:
In `@apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart`:
- Around line 27-118: The catch block in loadData (and similarly in
loadHistoryMore) swallows repository exceptions after emitting an error state;
update these methods (loadData and loadHistoryMore) to rethrow the caught
exception after calling emit (or add a throwOnError boolean parameter like
loadHistoryInitial and conditionally rethrow) so callers can react to failures;
ensure you reference the existing emit(state.copyWith(..., status:
TimeTrackerStatus.error, error: e.toString())) call and then rethrow the
Exception (or only rethrow when throwOnError is true) to preserve the current
state emission behavior.

In `@apps/mobile/lib/l10n/arb/app_en.arb`:
- Around line 167-272: The ARB additions in apps/mobile/lib/l10n/arb/app_en.arb
were not propagated to the generated localization outputs in
apps/mobile/lib/l10n/gen/*; regenerate the l10n outputs (e.g., run the project's
localization generation command such as the Flutter gen-l10n or intl_utils
generate task used by this repo) so that the new keys from app_en.arb appear in
the generated files, then add the updated files under apps/mobile/lib/l10n/gen/
to this PR; ensure the generation covers the new placeholders like
timerRequestActivityPageInfo and timerAllEditsRequireApproval.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 271-276: The DELETE query currently removed a session by id only;
add a defensive filter for workspace id to prevent TOCTOU or refactor
regressions by adding an equality filter for ws_id on the same query. Locate the
call chain created by createAdminClient() where
sbAdmin.from('time_tracking_sessions').delete().eq('id', sessionId) is used and
append an additional .eq('ws_id', wsId) to the delete query so the deletion is
scoped to the current workspace.
- Around line 24-63: Extract the repeated auth+workspace normalization logic
into a helper function (e.g., authenticateAndResolveWorkspace) that accepts
(request, wsId) and returns a successful result object { supabase, user,
normalizedWsId } or a NextResponse-like error; move the current sequence that
calls createClient(request), normalizeWorkspaceId(wsId, supabase),
supabase.auth.getUser(), and the workspace_members check into that helper, and
update the three handlers to call authenticateAndResolveWorkspace(...) and
early-return the error response if provided, otherwise use the returned
supabase, user, and normalizedWsId for the rest of the handler logic.
- Around line 219-287: The DELETE handler currently removes a session without
enforcing the same threshold/permission checks as PATCH; update the DELETE
function to call the same threshold validator (e.g.,
checkSessionThreshold(sessionId, normalizedWsId, sbAdmin) or equivalent) after
resolving normalizedWsId and before performing the admin delete, and also verify
the user’s bypass permission (bypass_time_tracking_request_approval) on their
workspace membership (same place you check memberCheck) so that if the session
exceeds the workspace's missed_entry_date_threshold you return a 403 (or the
same error/response PATCH uses) unless the member has the bypass permission;
keep using the existing symbols (DELETE, normalizeWorkspaceId,
createAdminClient, checkSessionThreshold, bypass_time_tracking_request_approval)
and throw/return consistent errors when checks fail before calling
sbAdmin.from('time_tracking_sessions').delete().

---

Duplicate comments:
In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 167-209: The switch on body.action can fall through and return
undefined for unsupported actions; ensure the default branch returns a 400 JSON
response using NextResponse.json({ error: `Unsupported action` }, { status: 400
}) in
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
(the switch that calls handleStopAction, handlePauseAction, handleResumeAction,
handleEditAction) and confirm NextResponse is imported so callers never receive
undefined.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c9b8cc and 510b880.

⛔ Files ignored due to path filters (3)
  • apps/mobile/lib/l10n/gen/app_localizations.dart is excluded by !**/gen/**
  • apps/mobile/lib/l10n/gen/app_localizations_en.dart is excluded by !**/gen/**
  • apps/mobile/lib/l10n/gen/app_localizations_vi.dart is excluded by !**/gen/**
📒 Files selected for processing (15)
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/history_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/threshold.ts

Comment thread apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
Copy link
Copy Markdown
Member

@vhpx vhpx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve AI comments

- Introduced compact navigation label handling and adjusted icon sizes for better UI consistency.
- Refactored pointer event handling to enhance responsiveness in the apps tab.
- Added a new method to streamline the opening of the apps drawer from the apps tab.
- Updated the TimeTrackerCubit to include error handling options and improved session management logic.
- Enhanced API routes for time tracking sessions with better authentication and error handling.
- Updated schemas to enforce ISO date formats for session timestamps.
@vercel vercel Bot temporarily deployed to Preview – track February 24, 2026 06:10 Inactive
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart">

<violation number="1" location="apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart:73">
P2: Guard the post-frame callback with `mounted` before using `context` to avoid accessing a disposed State.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/mobile/lib/features/shell/view/shell_page.dart (1)

263-307: ⚠️ Potential issue | 🟡 Minor

Use context.mounted after awaits (guideline).

After await in Line 265 and Line 304, the checks use mounted instead of context.mounted. The guideline explicitly requires context.mounted when guarding BuildContext usage after awaits.

✅ Proposed update
-    if (!mounted) {
+    if (!context.mounted) {
       return;
     }
-    if (!mounted) {
+    if (!context.mounted) {
       return;
     }

As per coding guidelines: "Capture dependencies from context before the first await in Flutter async methods and guard BuildContext usage after await with if (!context.mounted) return;."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/shell/view/shell_page.dart` around lines 263 - 307,
The async methods _openAppsDrawerFromAppsTab and _onItemTapped capture
context-derived values (e.g., appTabCubit via context.read<AppTabCubit>() and
state.hasSelection/appRoute) but currently use mounted checks after awaits;
change those guards to if (!context.mounted) return; and ensure any context
reads needed after awaits are captured before the first await (e.g., read
appTabCubit and compute appRoute/state-dependent values before calling await
appTabCubit.clearSelection() or any other await). Specifically update the
mounted checks in _openAppsDrawerFromAppsTab and both await-return guards in
_onItemTapped to use context.mounted and pre-fetch context dependencies before
awaits.
♻️ Duplicate comments (2)
apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart (2)

23-36: cardWidth clamping correctly applied.

The math.min(baseWidth, math.max(minCardWidth, ...)) guard from the previous review is now in place — no overflow can occur even when the container is narrower than minCardWidth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart` around lines
23 - 36, The cardWidth calculation in the LayoutBuilder already properly clamps
widths to avoid overflow by using math.min(baseWidth, math.max(minCardWidth,
availableWidth / 2)); no further fix is required—keep the current logic in the
builder (variables: LayoutBuilder, cardWidth, availableWidth, minCardWidth,
fallbackTotalWidth) as-is and merge the change.

116-133: Typography hierarchy correctly implemented.

theme.typography.p (bold) for the value and theme.typography.textSmall (muted) for the label now align with the _OverviewCard pattern — consistent with the previous review suggestion (addressed in commit 7c9b8cc).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart` around lines
116 - 133, The PR already applies the correct typography (using
theme.typography.p for the value and theme.typography.textSmall for the label
matching the _OverviewCard pattern), so remove the duplicate review tag/comment
and mark the change as approved; no code changes required — just resolve the
duplicate_comment/approve_code_changes metadata on the review thread referencing
the Text(value...) and Text(label...) usages and close the review.
🤖 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/mobile/lib/features/shell/view/shell_page.dart`:
- Around line 218-230: The Apps nav item uses _appsKey when useGlobalKey is
false but selectedKey is computed by _keyForIndex which always returns
_appsTabKey, so the Apps tab never shows selected in non-global mode; update the
selection logic by making _keyForIndex aware of useGlobalKey (add a parameter or
read the same flag) and return _appsKey when building for non-global layout, or
alternatively override selectedKey in _buildSideNavLayout to return _appsKey
when useGlobalKey is false (keep _keyForIndex behavior unchanged for other
callers). Ensure references to _appsKey, _appsTabKey, useGlobalKey, _keyForIndex
and _buildSideNavLayout are updated so the selectedKey matches the actual
NavigationItem key used.

In `@apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart`:
- Around line 260-261: setHistoryViewMode updates _historyFirstDayOfWeek but
then returns early when state.historyViewMode == viewMode, preventing
loadHistoryInitial from running if only the locale's firstDayOfWeek changed.
Modify setHistoryViewMode so that after assigning _historyFirstDayOfWeek it
still calls loadHistoryInitial when the first-day-of-week changed even if
viewMode is unchanged: compare the new firstDayOfWeek to the previous value and
invoke loadHistoryInitial() (or re-run the logic that refreshes the displayed
range) whenever the first-day-of-week differs; keep the existing early return
only when neither the view mode nor the first-day-of-week changed.
- Around line 738-742: The _historyPreferencesLoaded boolean is set
synchronously before awaiting SharedPreferences.getInstance(), causing
concurrent callers to short-circuit and read stale state; replace that flag with
a Future<void>? (e.g. _historyPreferencesLoadFuture) so multiple callers await
the same in-progress work: if the future is null, assign it to an async function
that awaits SharedPreferences.getInstance(), reads
prefs.getBool(_historyStatsAccordionPrefsKey) ?? false, sets
isHistoryStatsAccordionOpen and emits the state, then clears the future on
completion; remove the old _historyPreferencesLoaded field and keep the static
const _historyStatsAccordionPrefsKey as-is.

In `@apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart`:
- Around line 73-85: Add a mounted guard as the first statement inside the
WidgetsBinding.instance.addPostFrameCallback closure to avoid using a stale
BuildContext; i.e., inside that closure check "if (!mounted) return;" before
calling context.read<WorkspaceCubit>() or supabase.auth.currentUser, and capture
any context-dependent values (wsId via WorkspaceCubit.state.currentWorkspace?.id
and userId via supabase.auth.currentUser?.id) before any awaited work, then call
TimeTrackerCubit.loadData(wsId, userId, firstDayOfWeek:
_firstDayOfWeek(context)) only after the mounted check.

In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart`:
- Around line 38-72: The Wrap causes uneven card heights because _StatCard only
renders the icon when provided; update _StatCard.build to always reserve the
icon row space (e.g., render a fixed-size icon container or an Icon with
transparent color / a SizedBox matching the icon+padding height) so cards
without an icon (like "This Month") keep the same intrinsic height as those with
icon (like the "Streak" card); ensure the placeholder uses the same
dimensions/alignment as the real icon and lives in the same widget tree position
as the existing icon parameter.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts:
- Around line 75-121: The rollback after a failed break insert doesn't restore
the session's pending_approval flag set by the RPC pause_session_for_break;
update the rollback in the pause action to also reset pending_approval (the RPC
call is sbAdmin.rpc('pause_session_for_break') and the rollback runs via
sbAdmin.from('time_tracking_sessions').update(...).eq('id', sessionId)) —
include pending_approval: false (or the original previous value if available) in
that .update payload so the session is fully restored when break creation fails;
long-term, consider moving pause_session_for_break + break insert into a single
server-side transaction/RPC for atomicity.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts:
- Line 53: The server-side insert uses a hardcoded English fallback for the
title (title: session.title ?? 'Work session'); remove the literal fallback so
the server does not inject a locale-specific string—either omit the fallback
entirely and write title: session.title (letting the client provide a title) or
replace it with a locale-neutral token/constant; update the resume
handler/insert logic that sets the title field accordingly (reference: the title
property in the resume action where session.title is used).

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 99-102: The PATCH handler currently treats a missing permission
from getPermissions({ wsId: normalizedWsId, request }) as a 404; change it to
mirror the DELETE behavior by returning a 403 with a "Permissions not found"
message. Locate the permissions check in the PATCH flow (the variable
permissions and the NextResponse.json call) and replace the 404 response
payload/status with the same 403 response used in the DELETE branch so both
handlers consistently signal permission failures.
- Line 107: The unsafe cast "const sessionRecord = session as SessionRecord"
masks possible type drift between the Supabase-generated type and the
hand-maintained SessionRecord; replace this by either (preferred)
importing/deriving the DB type from packages/types/src/db.ts so the handler uses
the authoritative Supabase-derived type (remove the cast), or (short‑term)
validate and narrow the specific fields you rely on (e.g., check/convert
session.is_running to a non-null boolean and coerce/map other fields into a
validated SessionRecord object) before using them (and add a brief comment
justifying any remaining cast). Also update any downstream use sites (e.g.,
stop.ts where you read session.is_running) to use the validated/mapped object
rather than the raw cast.
- Around line 218-247: The code currently calls normalizeWorkspaceId before
authenticating the user, causing unnecessary DB work for unauthenticated
requests; reorder so you call supabase.auth.getUser() (from the supabase client
returned by createClient(request)) immediately after createClient and return the
401 on authError or missing user, then call normalizeWorkspaceId(wsId, supabase)
and keep the existing 404 handling for missing/failed normalization; ensure you
still use the same supabase instance and preserve the existing NextResponse.json
error responses and variable name normalizedWsId.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/schemas.ts:
- Around line 57-70: Replace the hand-written SessionRecord interface with the
canonical DB row type from packages/types/src/db.ts: remove the manual
SessionRecord declaration in schemas.ts and import the appropriate type that
represents the time_tracking_sessions row (use the exported name that maps to
time_tracking_sessions in packages/types, then use that type wherever
SessionRecord was used, including any casts). Ensure all references to
SessionRecord are updated to the imported DB type to prevent schema drift.

---

Outside diff comments:
In `@apps/mobile/lib/features/shell/view/shell_page.dart`:
- Around line 263-307: The async methods _openAppsDrawerFromAppsTab and
_onItemTapped capture context-derived values (e.g., appTabCubit via
context.read<AppTabCubit>() and state.hasSelection/appRoute) but currently use
mounted checks after awaits; change those guards to if (!context.mounted)
return; and ensure any context reads needed after awaits are captured before the
first await (e.g., read appTabCubit and compute appRoute/state-dependent values
before calling await appTabCubit.clearSelection() or any other await).
Specifically update the mounted checks in _openAppsDrawerFromAppsTab and both
await-return guards in _onItemTapped to use context.mounted and pre-fetch
context dependencies before awaits.

---

Duplicate comments:
In `@apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart`:
- Around line 23-36: The cardWidth calculation in the LayoutBuilder already
properly clamps widths to avoid overflow by using math.min(baseWidth,
math.max(minCardWidth, availableWidth / 2)); no further fix is required—keep the
current logic in the builder (variables: LayoutBuilder, cardWidth,
availableWidth, minCardWidth, fallbackTotalWidth) as-is and merge the change.
- Around line 116-133: The PR already applies the correct typography (using
theme.typography.p for the value and theme.typography.textSmall for the label
matching the _OverviewCard pattern), so remove the duplicate review tag/comment
and mark the change as approved; no code changes required — just resolve the
duplicate_comment/approve_code_changes metadata on the review thread referencing
the Text(value...) and Text(label...) usages and close the review.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 510b880 and 01683f6.

📒 Files selected for processing (9)
  • apps/mobile/lib/features/shell/view/shell_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build / build
  • GitHub Check: Build iOS (Development, simulator)
  • GitHub Check: Build Android APK (Development)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Playwright E2E
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Run tests and collect coverage
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Type Check
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (15)
apps/mobile/lib/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

apps/mobile/lib/**/*.dart: Use flutter_bloc with Cubits for feature state management in Flutter app
Use go_router with auth-aware redirects and ShellRoute for bottom tabs in Flutter app
Use supabase_flutter with flutter_secure_storage for token persistence in Flutter app
Follow very_good_analysis linting rules in Flutter app
Use on Exception catch (e) instead of bare catch in Flutter code; avoid catching Error subclasses
Capture context dependencies before first await and guard BuildContext usage after await with if (!context.mounted) return; in Flutter
Never return from a finally block in Dart/Flutter code
Use Future<void> Function() callbacks for mutation-driven UI actions in Flutter; await before closing dialogs

apps/mobile/lib/**/*.dart: When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the @ check and any email-specific success note (use TextInputType.emailAddress or an explicit parameter).
Use on Exception catch (e) (or specific exception types) instead of bare catch, avoid catching Error subclasses like TypeError, capture context dependencies before the first await, guard BuildContext usage after await with if (!context.mounted) return;, and do not return inside finally blocks.
The Flutter mobile app uses BLoC/Cubit state management, go_router navigation, and supabase_flutter for auth. Build flavors: main_development.dart, main_staging.dart, main_production.dart.
For mutation-driven Flutter UI actions (approve/reject/update), use Future<void> Function() callbacks (not VoidCallback), await them before closing dialogs/sheets, and surface failures in the UI (e.g., SnackBar).

apps/mobile/lib/**/*.dart: Use on Exception catch (e) with specific exception types in Flutter; avoid untyped catch blocks
Check context.mounted before using BuildContext after await in Flutter to preven...

Files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/shell/view/shell_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
apps/mobile/**/*.dart

📄 CodeRabbit inference engine (GEMINI.md)

Always run bun check:mobile after changes to apps/mobile/. This runs dart format --set-exit-if-changed lib test && flutter analyze && flutter test. All three checks MUST pass. If it reports a Dart format failure, rerun to confirm a clean pass.

apps/mobile/**/*.dart: Run dart format --set-exit-if-changed lib test in apps/mobile/ to verify code formatting; fix with dart format lib test
Run bun check:mobile (dart format + flutter analyze + flutter test) after making changes to apps/mobile/

Files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/shell/view/shell_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript with explicit return types for exported functions
Prefer interface for defining object shapes in TypeScript
Use TanStack Query (React Query) for ALL client-side data fetching - NEVER use useEffect for data fetching
Always include cache: 'no-store' in fetch() calls within TanStack Query queryFn to prevent dual-layer caching conflicts
Use Server Components by default; add 'use client' only when necessary
Use @tuturuuu/ui/sonner for toast notifications instead of the deprecated @tuturuuu/ui/toast
Refactor files exceeding 400 LOC and components exceeding 200 LOC into smaller, focused units
Extract complex logic to separate utilities and custom hooks following single responsibility principle
Import database types from packages/types/src/db.ts instead of manually defining database types (only after user runs migrations via bun sb:push and typegen via bun sb:typegen)
Always use stable array query keys with structure: [domain, subdomain?, paramsHash, version?] in TanStack Query
Use discriminated unions over enums for TypeScript type definitions
Narrow unknown/any at boundaries; justify with comments if needed
Query public.user_private_details table for user email addresses instead of public.users table
Reference environment variables by name only in code - never echo values
Prefer interface over type for defining object shapes in TypeScript
Use camelCase for variable and function names in TypeScript/JavaScript
Use SCREAMING_SNAKE_CASE for constants

**/*.{ts,tsx}: Always prefer importing database types from packages/types/src/db.ts. Never attempt to run migrations yourself - only after user runs bun sb:push and bun sb:typegen.
The public.users table does NOT contain an email field. User email addresses are stored in public.user_private_details. When querying user email, always use user_private_details table, not users.

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript
N...

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/*/src/app/api/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/app/api/**/*.ts: Use Zod for runtime validation of external inputs at API boundaries
Validate inputs early in API routes and return 400/401/403 for bad requests
Never leak stack traces to client; wrap external service calls and surface sanitized error messages
Log detailed errors server-side only; never expose sensitive information to client
Define and export runtime if edge is required: export const runtime = 'edge'
Use @tuturuuu/supabase client for authentication in API routes
Use normalizeWorkspaceId() helper to resolve workspace ID parameters in API routes
Enforce auth on AI endpoints by getting user via createClient() and returning 401 if absent
Check feature flags in workspace_secrets before serving AI features
Use Vercel AI SDK streamObject/generateObject with selected model for AI operations
Set maxDuration for long-running AI operations
Never log secrets or raw provider responses in AI endpoints

apps/*/src/app/api/**/*.ts: Use createClient(request) pattern in API routes to support both Bearer token auth (mobile) and cookie auth (web)
Use export const runtime = 'edge' to explicitly target edge runtime execution in Next.js API routes when appropriate
Use Zod for runtime validation of external inputs (API bodies, env-derived config) at system boundaries
Resolve workspace ID parameters (wsId) to UUIDs before database operations; use normalizeWorkspaceId() for special identifiers like 'personal' and 'internal'
Validate inputs early in API routes; return 4xx errors before processing to fail fast
Add export const runtime = 'edge' for routes that should execute on the edge runtime
Use Vercel AI SDK generateObject / streamObject with Zod schema for deterministic AI output structure
Use createAdminClient() (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/**/*.{ts,tsx}: Extract functions used ≥2 times to src/lib/ or src/utils/
Extract complex state logic to custom hooks in src/hooks/
Extract pure computations as testable utilities

apps/*/src/**/*.{ts,tsx}: Never log secrets, API keys, tokens, or raw sensitive data to console; only reference environment variable names
Query the public.user_private_details table for user email addresses; the public.users table does NOT contain an email field

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

Always use bun type-check command for type checking. Do NOT use npx tsgo, bunx tsgo, or other alternatives.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
**/*.{ts,tsx,js,jsx,json,yaml,yml}

📄 CodeRabbit inference engine (GEMINI.md)

Use Biome for linting and formatting. Run bun format-and-lint:fix to automatically fix issues.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
**/{src,lib}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Files >400 LOC and components >200 LOC should be refactored into smaller, focused units. Apply best practices to ALL code, regardless of age. Follow single responsibility principle, extract utilities/hooks for complex logic, and leave code better than you found it.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

apps/web/**/*.{ts,tsx}: Never use useEffect for data fetching. MANDATORY: Use TanStack Query (useQuery, useMutation, useInfiniteQuery) for all client-side data fetching.
Never use raw fetch() without TanStack Query wrapper in client components. Always use TanStack Query hooks for client-side data fetching.
Any fetch() inside a queryFn MUST include cache: 'no-store' to prevent browser HTTP cache from serving stale responses after TanStack Query invalidation.
Use stable array query keys following pattern: [domain, subdomain?, paramsHash, version?]. Set staleTime > 0 for rarely-changing data. Implement optimistic updates with rollback. Use narrow invalidations, avoid global invalidateQueries().
Follow the Tailwind Dynamic Color Policy. Never use hard-coded color classes like text-blue-500. Instead, use the dynamic-* tokens, e.g., text-dynamic-blue.
Use sonner for toasts: import { toast } from '@tuturuuu/ui/sonner';. Avoid the deprecated @tuturuuu/ui/toast. Never use native browser dialogs like alert() or confirm().
Use the dialog system for modals: import { Dialog, ... } from '@tuturuuu/ui/dialog';. Never use native browser dialogs.
Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries. Use normalizeWorkspaceId(wsId) helper for API routes or accept workspace prop in components.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

When adding new pages or routes, ALWAYS update the main navigation file (apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx). Add routes to both the aliases array and children navigation items with proper icons, permission checks, and translation keys in both en.json AND vi.json.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/*/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Create new API routes in apps/<app>/src/app/api/.... Use Supabase client wrappers for authentication and Zod for input validation.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/web/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

The mobile app connects to apps/web API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use createClient(request) for Bearer token auth.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Extract utilities to src/lib/, hooks to src/hooks/, and sub-components as needed. Apply best practices to BOTH old and new code. Follow single responsibility principle, use meaningful names, and eliminate duplication.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Avoid using any type in TypeScript; narrow unknown at boundaries with explicit type guards
Use discriminated unions over enums unless runtime enum is required
Always use bun type-check for TypeScript type checking; do NOT use npx tsgo, bunx tsgo, or other alternatives

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
apps/mobile/lib/**/*{cubit,bloc}.dart

📄 CodeRabbit inference engine (GEMINI.md)

If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
🧠 Learnings (31)
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to **/*.{tsx,css} : Avoid hard-coded palette utility classes (e.g., `text-blue-500`, `bg-purple-300/10`); use Tailwind dynamic color tokens instead (e.g., `text-dynamic-blue`, `bg-dynamic-purple/10`)

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widgets using `shadcn_flutter` with `shad.ShadcnApp` in tests to provide theme context

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : The Flutter mobile app uses BLoC/Cubit state management, `go_router` navigation, and `supabase_flutter` for auth. Build flavors: `main_development.dart`, `main_staging.dart`, `main_production.dart`.

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/**/test/**/*.dart : Wrap widgets that use `shadcn_flutter` in `shad.ShadcnApp` with `shad.ShadcnLocalizations.delegate` so `shad.Theme.of(context)` resolves in tests.

Applied to files:

  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Set `maxDuration` for long-running AI operations

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Capture `context` dependencies before first `await` and guard `BuildContext` usage after `await` with `if (!context.mounted) return;` in Flutter

Applied to files:

  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `flutter_bloc` with Cubits for feature state management in Flutter app

Applied to files:

  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : When refactoring duplicated Flutter editable fields into shared widgets, preserve per-field validation and success messaging. Email fields should keep the `@` check and any email-specific success note (use `TextInputType.emailAddress` or an explicit parameter).

Applied to files:

  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/test/**/*.dart : Wrap Flutter widget tests rendering `shadcn_flutter` components with `shad.ShadcnApp` including `shad.ShadcnLocalizations.delegate`

Applied to files:

  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `normalizeWorkspaceId()` helper to resolve workspace ID parameters in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/**/src/components/**/*.{ts,tsx} : Use `workspace.id` directly from workspace object in client components instead of raw `wsId` parameter

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Resolve workspace ID parameters (`wsId`) to UUIDs before database operations; use `normalizeWorkspaceId()` for special identifiers like 'personal' and 'internal'

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to scripts/**/*.{js,ts} : Use workspace-relative paths in `apply_patch` (e.g., `apps/web/...`) instead of absolute Windows paths for better cross-platform compatibility

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Database `ws_id` columns ALWAYS store UUIDs. Route parameters (`wsId`) may contain special identifiers like `personal` or `internal`. NEVER use raw `wsId` directly in database queries. Use `normalizeWorkspaceId(wsId)` helper for API routes or accept `workspace` prop in components.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to packages/ui/src/**/*.{ts,tsx} : Extract portable UI components with no `@/` imports to `packages/ui`. Pass `workspace` prop to child components instead of raw `wsId`.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation file when adding new routes: add entries to both `aliases` array and `children` navigation items with proper icons and permission checks

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation in `apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx` when adding new routes - add to both `aliases` array and `children` navigation items with proper icons and permissions

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/src/app/api/**/*.{ts,tsx} : The mobile app connects to `apps/web` API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use `createClient(request)` for Bearer token auth.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to **/*.{ts,tsx} : Narrow `unknown`/`any` at boundaries; justify with comments if needed

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to packages/ai/src/object/types.ts : Define AI endpoint schemas in `packages/ai/src/object/types.ts` using Zod

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use Zod for runtime validation of external inputs (API bodies, env-derived config) at system boundaries

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use Zod for runtime validation of external inputs at API boundaries

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `go_router` with auth-aware redirects and ShellRoute for bottom tabs in Flutter app

Applied to files:

  • apps/mobile/lib/features/shell/view/shell_page.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Check `context.mounted` before using `BuildContext` after `await` in Flutter to prevent errors

Applied to files:

  • apps/mobile/lib/features/shell/view/shell_page.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*{cubit,bloc}.dart : If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `on Exception catch (e)` (or specific exception types) instead of bare `catch`, avoid catching `Error` subclasses like `TypeError`, capture `context` dependencies before the first `await`, guard `BuildContext` usage after `await` with `if (!context.mounted) return;`, and do not `return` inside `finally` blocks.

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `on Exception catch (e)` instead of bare `catch` in Flutter code; avoid catching `Error` subclasses

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `Future<void> Function()` callbacks for widget actions depending on server mutations; await them and only close UI on success

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `Future<void> Function()` callbacks for mutation-driven UI actions in Flutter; await before closing dialogs

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/mobile/lib/**/*.dart : For mutation-driven Flutter UI actions (approve/reject/update), use `Future<void> Function()` callbacks (not `VoidCallback`), await them before closing dialogs/sheets, and surface failures in the UI (e.g., `SnackBar`).

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/mobile/lib/**/*.dart : Use `supabase_flutter` with `flutter_secure_storage` for token persistence in Flutter app

Applied to files:

  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
🧬 Code graph analysis (2)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts (3)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts (3)
  • handleStopAction (5-5)
  • SessionRecord (11-11)
  • checkSessionThreshold (18-18)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts (1)
  • SessionRecord (57-70)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/threshold.ts (1)
  • checkSessionThreshold (25-101)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts (2)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts (2)
  • handleResumeAction (3-3)
  • SessionRecord (11-11)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts (1)
  • SessionRecord (57-70)
🔇 Additional comments (8)
apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart (3)

756-832: Month navigation fix and locale-aware week range — past blockers resolved ✓

_moveHistoryAnchor now uses DateTime(current.year, current.month + delta, 1, ...) to let Dart normalize the month/year, eliminating the previous truncation-division bug. _historyPeriodRange's week branch correctly uses (normalizedAnchor.weekday - firstDayOfWeek + 7) % 7 and anchor.toLocal() replaces the redundant field-reconstruction. All three previously flagged blockers are resolved.


332-402: loadHistoryInitial: rethrow and record .wait — past issues resolved ✓

throwOnError rethrow is now present (lines 398–400) and the parallel fetch uses the type-safe record .wait pattern (lines 364–377). Both previously flagged issues are closed.


839-848: _loadRecentAndSummary always fetches fresh data — past issue resolved ✓

The previous stale-stats-on-null-userId fallback is gone; _repo.getStats(wsId, userId) is always called. Record .wait ensures parallel execution with correct types.

apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart (1)

25-39: _firstDayOfWeek deduplication — past comment addressed ✓

The helper is now a shared top-level function, eliminating the three-class duplication previously flagged.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts (1)

69-75: Break-close failure now properly aborts the stop — previous issue resolved.

The 500 early-return on updateError prevents the session from being stopped while an open break lingers in the database.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts (1)

30-44: Break-close failure now properly aborts the resume — previous issue resolved.

Returning 500 on updateError prevents the new resumed session from being created while an open break still overlaps it.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)

145-149: Exhaustive default case added — previous issue resolved.

The switch now has a fallback that returns a 400 for unrecognized actions, preventing a silent undefined response if the discriminated union is ever extended.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts (1)

42-43: The code at lines 42–43 is correct and will work as intended. The project is already on Zod v4.3.6 across all workspaces, so z.iso.datetime() is available and valid. No runtime errors will occur.

However, SessionRecord (lines 57–70) manually defines database field types. Per coding guidelines, import database types from packages/types/src/db.ts instead.

Likely an incorrect or invalid review comment.

Comment thread apps/mobile/lib/features/shell/view/shell_page.dart
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart Outdated
Comment thread apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
Comment thread apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (3)

107-107: ⚠️ Potential issue | 🟠 Major

Unsafe as SessionRecord cast still present — previously flagged.

The Supabase-generated type and the hand-maintained SessionRecord interface can drift silently. If is_running is boolean | null in the DB type but boolean in SessionRecord, downstream handlers (e.g., stop.ts) misinterpret null as false. Prefer importing the generated type from packages/types/src/db.ts, or at minimum add a justifying comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
at line 107, Replace the unsafe "const sessionRecord = session as
SessionRecord;" cast: import and use the Supabase-generated type from
packages/types/src/db.ts (the generated Session type) instead of the
hand-maintained SessionRecord, and update the variable declaration to that
generated type so nullable DB fields (e.g., is_running: boolean | null) are
preserved; if importing the generated type is impossible, add a concise comment
next to the cast naming the exact reason for the manual assertion and the
invariants that guarantee non-nullability (and add a TODO to remove the
assertion when types are aligned).

100-102: ⚠️ Potential issue | 🟡 Minor

PATCH returns 404 for a missing-permissions result while DELETE correctly returns 403 (line 187).

This inconsistency was flagged in a previous review and remains unaddressed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 100 - 102, The PATCH handler currently returns NextResponse.json({
error: 'Not found' }, { status: 404 }) when permissions is falsy, causing
inconsistency with DELETE which returns 403; change the PATCH permission check
to return a 403 Forbidden (e.g., NextResponse.json({ error: 'Forbidden' }, {
status: 403 })) when permissions is missing so both handlers use the same
permission-denied status; update the branch that checks the permissions variable
in the PATCH flow (the block that currently returns 404) to return 403 and a
matching error message.

221-250: ⚠️ Potential issue | 🟡 Minor

Authentication is still checked after normalizeWorkspaceId — previously flagged.

normalizeWorkspaceId (line 224) issues a DB round-trip before supabase.auth.getUser() (line 242), causing unnecessary work for every unauthenticated request. Moving the auth check first is both cheaper and a more conventional defence-in-depth ordering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts
around lines 221 - 250, The code calls normalizeWorkspaceId (which performs a DB
round-trip) before verifying the requester, causing wasted DB work for
unauthenticated calls; change the order so you call supabase.auth.getUser() (via
createClient and supabase.auth.getUser()) and return the 401 NextResponse.json
on authError or missing user before calling normalizeWorkspaceId, then only call
normalizeWorkspaceId and check normalizedWsId when the user is authenticated;
update error returns to use NextResponse.json consistently for both auth and
workspace-not-found cases.
🤖 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/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Around line 22-24: The exported route handler functions GET, PATCH, and DELETE
currently lack explicit return type annotations; update each function signature
to declare its return type as Promise<NextResponse> (e.g., export async function
GET(...): Promise<NextResponse>), keeping existing parameters and implementation
unchanged so the handlers conform to the project's TypeScript coding guidelines.
- Around line 233-240: The check for falsy normalizedWsId after
normalizeWorkspaceId is redundant and unreachable because normalizeWorkspaceId
either throws (already handled in the catch that returns) or returns a valid
string; remove the entire if (!normalizedWsId) block to simplify flow, keeping
the existing try/catch that handles errors from normalizeWorkspaceId and relying
on the returned normalizedWsId for subsequent logic in this route handler.
- Around line 192-196: The admin DELETE currently omits the user constraint
which creates a TOCTOU risk after the ownership check performed with the
RLS-scoped supabase client; update the sbAdmin.delete call against the
time_tracking_sessions table to include .eq('user_id', userId) (using the same
userId checked by the ownership logic) so the admin query narrows by id, ws_id
(normalizedWsId) and user_id, preventing deletion of another user's session even
if state changes between the check and delete.

---

Duplicate comments:
In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/sessions/[sessionId]/route.ts:
- Line 107: Replace the unsafe "const sessionRecord = session as SessionRecord;"
cast: import and use the Supabase-generated type from packages/types/src/db.ts
(the generated Session type) instead of the hand-maintained SessionRecord, and
update the variable declaration to that generated type so nullable DB fields
(e.g., is_running: boolean | null) are preserved; if importing the generated
type is impossible, add a concise comment next to the cast naming the exact
reason for the manual assertion and the invariants that guarantee
non-nullability (and add a TODO to remove the assertion when types are aligned).
- Around line 100-102: The PATCH handler currently returns NextResponse.json({
error: 'Not found' }, { status: 404 }) when permissions is falsy, causing
inconsistency with DELETE which returns 403; change the PATCH permission check
to return a 403 Forbidden (e.g., NextResponse.json({ error: 'Forbidden' }, {
status: 403 })) when permissions is missing so both handlers use the same
permission-denied status; update the branch that checks the permissions variable
in the PATCH flow (the block that currently returns 404) to return 403 and a
matching error message.
- Around line 221-250: The code calls normalizeWorkspaceId (which performs a DB
round-trip) before verifying the requester, causing wasted DB work for
unauthenticated calls; change the order so you call supabase.auth.getUser() (via
createClient and supabase.auth.getUser()) and return the 401 NextResponse.json
on authError or missing user before calling normalizeWorkspaceId, then only call
normalizeWorkspaceId and check normalizedWsId when the user is authenticated;
update error returns to use NextResponse.json consistently for both auth and
workspace-not-found cases.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01683f6 and 3561aa6.

📒 Files selected for processing (1)
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build iOS (Development, simulator)
  • GitHub Check: Playwright E2E
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript with explicit return types for exported functions
Prefer interface for defining object shapes in TypeScript
Use TanStack Query (React Query) for ALL client-side data fetching - NEVER use useEffect for data fetching
Always include cache: 'no-store' in fetch() calls within TanStack Query queryFn to prevent dual-layer caching conflicts
Use Server Components by default; add 'use client' only when necessary
Use @tuturuuu/ui/sonner for toast notifications instead of the deprecated @tuturuuu/ui/toast
Refactor files exceeding 400 LOC and components exceeding 200 LOC into smaller, focused units
Extract complex logic to separate utilities and custom hooks following single responsibility principle
Import database types from packages/types/src/db.ts instead of manually defining database types (only after user runs migrations via bun sb:push and typegen via bun sb:typegen)
Always use stable array query keys with structure: [domain, subdomain?, paramsHash, version?] in TanStack Query
Use discriminated unions over enums for TypeScript type definitions
Narrow unknown/any at boundaries; justify with comments if needed
Query public.user_private_details table for user email addresses instead of public.users table
Reference environment variables by name only in code - never echo values
Prefer interface over type for defining object shapes in TypeScript
Use camelCase for variable and function names in TypeScript/JavaScript
Use SCREAMING_SNAKE_CASE for constants

**/*.{ts,tsx}: Always prefer importing database types from packages/types/src/db.ts. Never attempt to run migrations yourself - only after user runs bun sb:push and bun sb:typegen.
The public.users table does NOT contain an email field. User email addresses are stored in public.user_private_details. When querying user email, always use user_private_details table, not users.

**/*.{ts,tsx}: Prefer interface for defining object shapes in TypeScript
N...

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/app/api/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/app/api/**/*.ts: Use Zod for runtime validation of external inputs at API boundaries
Validate inputs early in API routes and return 400/401/403 for bad requests
Never leak stack traces to client; wrap external service calls and surface sanitized error messages
Log detailed errors server-side only; never expose sensitive information to client
Define and export runtime if edge is required: export const runtime = 'edge'
Use @tuturuuu/supabase client for authentication in API routes
Use normalizeWorkspaceId() helper to resolve workspace ID parameters in API routes
Enforce auth on AI endpoints by getting user via createClient() and returning 401 if absent
Check feature flags in workspace_secrets before serving AI features
Use Vercel AI SDK streamObject/generateObject with selected model for AI operations
Set maxDuration for long-running AI operations
Never log secrets or raw provider responses in AI endpoints

apps/*/src/app/api/**/*.ts: Use createClient(request) pattern in API routes to support both Bearer token auth (mobile) and cookie auth (web)
Use export const runtime = 'edge' to explicitly target edge runtime execution in Next.js API routes when appropriate
Use Zod for runtime validation of external inputs (API bodies, env-derived config) at system boundaries
Resolve workspace ID parameters (wsId) to UUIDs before database operations; use normalizeWorkspaceId() for special identifiers like 'personal' and 'internal'
Validate inputs early in API routes; return 4xx errors before processing to fail fast
Add export const runtime = 'edge' for routes that should execute on the edge runtime
Use Vercel AI SDK generateObject / streamObject with Zod schema for deterministic AI output structure
Use createAdminClient() (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/*/src/**/*.{ts,tsx}: Extract functions used ≥2 times to src/lib/ or src/utils/
Extract complex state logic to custom hooks in src/hooks/
Extract pure computations as testable utilities

apps/*/src/**/*.{ts,tsx}: Never log secrets, API keys, tokens, or raw sensitive data to console; only reference environment variable names
Query the public.user_private_details table for user email addresses; the public.users table does NOT contain an email field

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (GEMINI.md)

Always use bun type-check command for type checking. Do NOT use npx tsgo, bunx tsgo, or other alternatives.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.{ts,tsx,js,jsx,json,yaml,yml}

📄 CodeRabbit inference engine (GEMINI.md)

Use Biome for linting and formatting. Run bun format-and-lint:fix to automatically fix issues.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/{src,lib}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Files >400 LOC and components >200 LOC should be refactored into smaller, focused units. Apply best practices to ALL code, regardless of age. Follow single responsibility principle, extract utilities/hooks for complex logic, and leave code better than you found it.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

apps/web/**/*.{ts,tsx}: Never use useEffect for data fetching. MANDATORY: Use TanStack Query (useQuery, useMutation, useInfiniteQuery) for all client-side data fetching.
Never use raw fetch() without TanStack Query wrapper in client components. Always use TanStack Query hooks for client-side data fetching.
Any fetch() inside a queryFn MUST include cache: 'no-store' to prevent browser HTTP cache from serving stale responses after TanStack Query invalidation.
Use stable array query keys following pattern: [domain, subdomain?, paramsHash, version?]. Set staleTime > 0 for rarely-changing data. Implement optimistic updates with rollback. Use narrow invalidations, avoid global invalidateQueries().
Follow the Tailwind Dynamic Color Policy. Never use hard-coded color classes like text-blue-500. Instead, use the dynamic-* tokens, e.g., text-dynamic-blue.
Use sonner for toasts: import { toast } from '@tuturuuu/ui/sonner';. Avoid the deprecated @tuturuuu/ui/toast. Never use native browser dialogs like alert() or confirm().
Use the dialog system for modals: import { Dialog, ... } from '@tuturuuu/ui/dialog';. Never use native browser dialogs.
Database ws_id columns ALWAYS store UUIDs. Route parameters (wsId) may contain special identifiers like personal or internal. NEVER use raw wsId directly in database queries. Use normalizeWorkspaceId(wsId) helper for API routes or accept workspace prop in components.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

When adding new pages or routes, ALWAYS update the main navigation file (apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx). Add routes to both the aliases array and children navigation items with proper icons, permission checks, and translation keys in both en.json AND vi.json.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/*/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Create new API routes in apps/<app>/src/app/api/.... Use Supabase client wrappers for authentication and Zod for input validation.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

The mobile app connects to apps/web API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use createClient(request) for Bearer token auth.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Extract utilities to src/lib/, hooks to src/hooks/, and sub-components as needed. Apply best practices to BOTH old and new code. Follow single responsibility principle, use meaningful names, and eliminate duplication.

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Avoid using any type in TypeScript; narrow unknown at boundaries with explicit type guards
Use discriminated unions over enums unless runtime enum is required
Always use bun type-check for TypeScript type checking; do NOT use npx tsgo, bunx tsgo, or other alternatives

Files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
🧠 Learnings (14)
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `normalizeWorkspaceId()` helper to resolve workspace ID parameters in API routes

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/**/src/components/**/*.{ts,tsx} : Use `workspace.id` directly from workspace object in client components instead of raw `wsId` parameter

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Resolve workspace ID parameters (`wsId`) to UUIDs before database operations; use `normalizeWorkspaceId()` for special identifiers like 'personal' and 'internal'

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Database `ws_id` columns ALWAYS store UUIDs. Route parameters (`wsId`) may contain special identifiers like `personal` or `internal`. NEVER use raw `wsId` directly in database queries. Use `normalizeWorkspaceId(wsId)` helper for API routes or accept `workspace` prop in components.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to packages/ui/src/**/*.{ts,tsx} : Extract portable UI components with no `@/` imports to `packages/ui`. Pass `workspace` prop to child components instead of raw `wsId`.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to scripts/**/*.{js,ts} : Use workspace-relative paths in `apply_patch` (e.g., `apps/web/...`) instead of absolute Windows paths for better cross-platform compatibility

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation file when adding new routes: add entries to both `aliases` array and `children` navigation items with proper icons and permission checks

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx : Update main navigation in `apps/web/src/app/[locale]/(dashboard)/[wsId]/navigation.tsx` when adding new routes - add to both `aliases` array and `children` navigation items with proper icons and permissions

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:53.243Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: GEMINI.md:0-0
Timestamp: 2026-02-23T06:49:53.243Z
Learning: Applies to apps/web/src/app/api/**/*.{ts,tsx} : The mobile app connects to `apps/web` API routes via Bearer token auth (not cookies). When updating web API routes for mobile features, maintain backward compatibility and use `createClient(request)` for Bearer token auth.

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to **/*.{ts,tsx} : Narrow `unknown`/`any` at boundaries; justify with comments if needed

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to **/*.ts : Avoid using `any` type in TypeScript; narrow `unknown` at boundaries with explicit type guards

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.270Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.270Z
Learning: Applies to packages/types/src/supabase.ts : Never hand-edit generated type files in `packages/types/src/supabase.ts`

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:51:05.456Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T06:51:05.456Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Use `createAdminClient()` (sbAdmin) when bulk operations need to bypass trigger permission checks (e.g., user merge); always validate workspace membership first

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
📚 Learning: 2026-02-23T06:49:12.271Z
Learnt from: CR
Repo: tutur3u/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:49:12.271Z
Learning: Applies to apps/*/src/app/api/**/*.ts : Enforce auth on AI endpoints by getting user via `createClient()` and returning 401 if absent

Applied to files:

  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts

- Updated `_keyForIndex` method in `ShellPage` to allow for non-global key usage.
- Improved context handling in `TimeTrackerCubit` to ensure proper state management and loading of history preferences.
- Refactored `initState` in `TimeTrackerView` to safely access context and load data.
- Adjusted `StatCard` widget layout for better icon handling and consistency.
- Enhanced API error handling for time tracking sessions, including permission checks and session state validation.
@VNOsST VNOsST requested a review from vhpx February 24, 2026 06:28
@vhpx vhpx merged commit 5e8c666 into main Feb 24, 2026
69 checks passed
@vhpx vhpx deleted the feat/time-tracker-history-mobile branch February 24, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

calendar enhancement New feature or request platform Infrastructure changes tudo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants