Skip to content

refactor(time-tracker): time tracking proof images now uses signed URLs#4344

Merged
vhpx merged 8 commits intomainfrom
feat/signed-url-requests
Feb 24, 2026
Merged

refactor(time-tracker): time tracking proof images now uses signed URLs#4344
vhpx merged 8 commits intomainfrom
feat/signed-url-requests

Conversation

@VNOsST
Copy link
Copy Markdown
Collaborator

@VNOsST VNOsST commented Feb 24, 2026

Description

Switched time tracking proof image uploads to signed URLs and moved request APIs to JSON for safer, faster uploads. Also updated missed-entry UX, added tests, and kept the mobile history view.

  • New Features

    • Direct image uploads via signed URLs (mobile + web) using POST /api/v1/workspaces/[wsId]/time-tracking/requests/upload-url returning uploads[] with signedUrl, token, path.
    • Requests API (POST/PUT) now JSON; upload images first, then send storage paths; server validates paths belong to requestId and blocks traversal.
    • Mobile repo uploads via PUT to signed URLs with bearer token; client generates requestId on create; added unit tests.
    • Mobile history (day/week/month, stats, pagination) and improved missed-entry toasts/localization.
  • Migration

    • Replace FormData with JSON. Use the upload-url endpoint to get signed URLs, PUT files, then send image paths plus requestId on create; send newImagePaths on edit.
    • Mobile: rename imagePaths → imageLocalPaths and newImagePaths → newImageLocalPaths; pass local file paths to upload first.
    • Only allow png/jpg/jpeg/webp/gif; max 5 images on create. Paths not prefixed by requestId or containing ".." are rejected.

…tor request handling

Added functionality to upload time tracking images using signed URLs, enhancing the request mutation process. Refactored the request handling to accept JSON payloads instead of FormData, improving API consistency and error handling. Updated related components to support the new image upload mechanism.
Updated the time tracking repository and related components to use imageLocalPaths instead of imagePaths for better clarity. Implemented a new image upload flow using signed URLs, enhancing the request creation and update processes. Added unit tests to ensure the functionality of the new upload mechanism.
@VNOsST VNOsST self-assigned this Feb 24, 2026
@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 8:31am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/signed-url-requests

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.

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.

2 issues found across 41 files

Confidence score: 3/5

  • Potential authorization gap in apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts could let workspace members generate upload URLs for arbitrary request IDs, which is a concrete access-control risk.
  • Edit validation in apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts can allow invalid time intervals when only one bound is updated, which could lead to inconsistent session data.
  • These are medium-severity, user-impacting issues, so while the PR may be close, it carries some merge risk that should be addressed.
  • Pay close attention to apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts and apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts - authorization and time-range validation gaps.
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/edit.ts">

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts:52">
P2: Validation only checks start < end when both startTime and endTime are supplied. Updating just one field can create an invalid interval (e.g., new start after existing end). Validate against the existing session time when only one field is provided.</violation>
</file>

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

<violation number="1" location="apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts:93">
P1: Validate that requestId belongs to the current workspace/user before generating signed upload URLs. Without this check, any workspace member can request upload URLs for arbitrary request IDs and write into other users’ request folders.</violation>
</file>

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

@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 introduces a more secure and efficient way to handle image uploads for time tracking requests by leveraging signed URLs. It also significantly enhances the time tracking history view with period-based navigation and detailed statistics. Furthermore, the user experience for editing existing sessions and adding missed entries has been improved with clearer validation rules and immediate feedback, ensuring data integrity and compliance with workspace settings.

Highlights

  • Signed URL Image Uploads: Refactored the image upload mechanism for time tracking requests to utilize secure signed URLs, enhancing security and scalability by allowing direct uploads to storage services.
  • Time Tracking History & Stats: Introduced new features for viewing time tracking history, including period-based navigation (day, week, month) and an expandable accordion for period statistics, providing a comprehensive overview of tracked time.
  • Enhanced Session Editing: Improved the session editing dialog with new validation logic that restricts time edits based on workspace thresholds, requiring approval for older sessions, and added better user feedback with toast notifications.
  • API Refactoring: The backend API for time tracking sessions and requests has been significantly refactored, separating concerns into dedicated action handlers and introducing robust validation for image paths and session modifications.
Changelog
  • apps/mobile/lib/data/models/time_tracking/period_stats.dart
    • Added new data models for TimeTrackingPeriodBreakdown and TimeTrackingPeriodStats.
  • apps/mobile/lib/data/models/time_tracking/session_page.dart
    • Added a new data model for TimeTrackingSessionPage to handle paginated session results.
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
    • Modified to support signed URL image uploads for requests.
    • Added new methods: getHistorySessions and getPeriodStats.
    • Updated existing methods (createRequest, updateRequest) to use local image paths and the new upload mechanism.
    • Replaced http_parser with dart:io and http for file operations.
  • apps/mobile/lib/features/shell/view/shell_page.dart
    • Updated navigation bar item building to use new constants for icon size and label width.
    • Added _lastAppsTabPointerUpAt to track pointer events for double-tap logic.
    • Refactored _onItemTapped to use a new _openAppsDrawerFromAppsTab helper for clarity.
    • Introduced _handlePointerUp to manage pointer events and double-tap detection on the apps tab.
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
    • Removed TimeTrackingCategory import as it's no longer directly used in the cubit.
    • Added SharedPreferences import for persisting history accordion state.
    • Introduced new state properties and methods for managing time tracking history (view mode, anchor date, sessions, stats, loading states, accordion state).
    • Refactored loadData to fetch history sessions and period stats.
    • Added setHistoryViewMode, goToPreviousPeriod, goToNextPeriod, goToCurrentPeriod, refreshHistory, loadHistoryInitial, loadHistoryMore, and toggleHistoryStatsAccordion methods.
    • Updated stopSession, editSession, deleteSession, addMissedEntry, and discardRunningSession to refresh history data after modifications.
    • Added helper methods _ensureHistoryPreferencesLoaded, _moveHistoryAnchor, _historyPeriodRange, _normalizeUserId, and _loadRecentAndSummary.
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_requests_cubit.dart
    • Updated updateRequest method to accept newImageLocalPaths instead of newImagePaths.
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_state.dart
    • Added HistoryViewMode enum.
    • Introduced new state properties for history management: historyViewMode, historyAnchorDate, historySessions, historyPeriodStats, historyNextCursor, historyHasMore, isHistoryLoading, isHistoryLoadingMore, and isHistoryStatsAccordionOpen.
  • apps/mobile/lib/features/time_tracker/view/time_tracker_page.dart
    • Added _firstDayOfWeek helper function to determine the first day of the week based on locale.
    • Modified create method in BlocProvider to initialize TimeTrackerCubit without immediately loading data.
    • Moved initial data loading to initState of _TimeTrackerViewState to ensure context is mounted.
    • Updated onRefresh callback to pass firstDayOfWeek to loadData.
    • Wrapped shad.Tabs in Center widget for better alignment.
    • Updated _ErrorView retry logic to pass firstDayOfWeek to loadData.
  • apps/mobile/lib/features/time_tracker/view/time_tracker_requests_page.dart
    • Updated onEdit callback in _RequestsViewState to use newImageLocalPaths.
  • apps/mobile/lib/features/time_tracker/widgets/edit_request_dialog.dart
    • Updated onSave callback to accept newImageLocalPaths instead of newImagePaths.
  • apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart
    • Added thresholdDays parameter to EditSessionDialog constructor.
    • Introduced _isSubmitting state to manage button loading state.
    • Added logic to restrict time editing based on thresholdDays and display a warning message.
    • Updated _formatDuration to handle negative durations and use AppLocalizations.
    • Implemented toast notifications for successful session updates and errors.
    • Added enabled property to _DateTimePicker to control interactivity.
  • apps/mobile/lib/features/time_tracker/widgets/history_period_controls.dart
    • Added new widget HistoryPeriodControls for navigating time tracking history periods (day, week, month views).
  • apps/mobile/lib/features/time_tracker/widgets/history_stats_accordion.dart
    • Added new widget HistoryStatsAccordion to display time tracking period statistics in an expandable accordion.
  • 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.
    • Integrated HistoryPeriodControls and HistoryStatsAccordion into the history tab UI.
    • Updated session grouping logic (_groupByDay) to handle unknown dates and use AppLocalizations.
    • Modified onRefresh to call cubit.refreshHistory.
    • Added conditional rendering for loading indicators and 'end of list' messages.
    • Updated _showEditDialog and _deleteSession to use _currentUserId and firstDayOfWeek.
  • apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart
    • Updated onSave callback to accept imageLocalPaths instead of imagePaths.
    • Modified success and error toast messages to be more specific (timerRequestSubmittedTitle, timerMissedEntrySavedTitle, etc.).
    • Adjusted toast context to use rootNavigator for proper display.
  • apps/mobile/lib/features/time_tracker/widgets/request_detail_sheet.dart
    • Updated onEdit callback to accept newImageLocalPaths instead of newImagePaths.
  • apps/mobile/lib/features/time_tracker/widgets/stats_cards.dart
    • Added dart:math import.
    • Modified StatsCards to use LayoutBuilder and Wrap for responsive card layout.
    • Adjusted _StatCard to use SizedBox for icon height and updated typography styles.
  • apps/mobile/lib/features/time_tracker/widgets/timer_controls.dart
    • Changed 'Add missed entry' button from shad.GhostButton to shad.OutlineButton.
  • apps/mobile/lib/features/time_tracker/widgets/timer_tab.dart
    • Updated onAddMissedEntry callback to accept imageLocalPaths instead of imagePaths.
  • apps/mobile/lib/l10n/arb/app_en.arb
    • Added new localization keys: timerHistoryNoSessionsForPeriod, timerHistoryOverview, timerHistoryTotalTime, timerHistoryLoadMore, timerHistoryEndOfList, timerSessionUpdated, timerInvalidDuration, timerUnknownDate, timerRequestSubmittedTitle, timerRequestSubmittedContent, timerMissedEntrySavedTitle, timerMissedEntrySavedContent, timerTimeEditingRestricted, timerAllEditsRequireApproval.
  • apps/mobile/lib/l10n/arb/app_vi.arb
    • Added new Vietnamese localization keys corresponding to the English additions.
  • apps/mobile/lib/l10n/gen/app_localizations.dart
    • Updated generated AppLocalizations with new string getters and a parameterized method for timerAllEditsRequireApproval.
  • apps/mobile/lib/l10n/gen/app_localizations_en.dart
    • Updated English localization implementation with new strings.
  • apps/mobile/lib/l10n/gen/app_localizations_vi.dart
    • Updated Vietnamese localization implementation with new strings.
  • apps/mobile/test/data/repositories/time_tracker_repository_test.dart
    • Added new test file for TimeTrackerRepository.
    • Implemented tests for the signed URL image upload flow for createRequest and updateRequest.
  • apps/mobile/test/features/time_tracker/cubit/time_tracker_cubit_test.dart
    • Added setUpAll to register DateTime as a fallback value for mocktail.
    • Added setUp to mock SharedPreferences initial values.
    • Introduced new test group for TimeTrackerCubit history behavior.
    • Added tests for changing history view mode, loading initial history, loading more history, and persisting history stats accordion state.
  • apps/mobile/test/features/time_tracker/widgets/history_tab_test.dart
    • Added new test file for HistoryTab.
    • Implemented a test to ensure period controls remain visible during history loading.
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
    • Added uploadTimeTrackingImages helper function to handle image uploads via signed URLs.
    • Refactored useUpdateRequest to use uploadTimeTrackingImages and send image paths in the JSON body instead of multipart form data.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
    • Removed image-related constants (MAX_FILE_SIZE, ALLOWED_MIME_TYPES, EXTENSION_TO_MIME_TYPE).
    • Added editRequestSchema for validating JSON body of PUT requests.
    • Added validateRequestImagePaths function to ensure image paths are correctly formatted and prevent path traversal.
    • Modified PUT endpoint to expect JSON body with pre-uploaded image paths and removed multipart form data handling.
    • Updated image cleanup logic to use newImagePaths instead of uploadedImagePaths.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts
    • Removed image-related constants and sanitizeFilename import.
    • Updated TimeTrackingRequestSchema to include requestId, description, categoryId, taskId, breakTypeId, breakTypeName, linkedSessionId, and imagePaths.
    • Added validateImagePaths function for image path validation.
    • Modified POST endpoint to expect JSON body with pre-uploaded image paths and removed multipart form data handling.
    • Updated image cleanup logic to use parsed.imagePaths instead of uploadedImagePaths.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
    • Added new API route to generate signed URLs for time tracking request image uploads.
    • Implemented validation for filename, request ID, and allowed image extensions.
    • Uses createDynamicAdminClient to generate signed upload URLs with tokens and paths.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/edit.ts
    • Added new file edit.ts to encapsulate the logic for handling 'edit' actions on time tracking sessions, including time validation, threshold checks, and bypass logic.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/pause.ts
    • Added new file pause.ts to encapsulate the logic for handling 'pause' actions on time tracking sessions, including threshold checks, break record creation, and RPC calls.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/resume.ts
    • Added new file resume.ts to encapsulate the logic for handling 'resume' actions on time tracking sessions, including closing active breaks and creating new session segments.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/actions/stop.ts
    • Added new file stop.ts to encapsulate the logic for handling 'stop' actions on time tracking sessions, including threshold checks and closing active breaks.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/helpers.ts
    • Added new file helpers.ts to export action handlers, schemas, and threshold utilities for time tracking sessions.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts
    • Removed inline helper functions for getSessionChainRoot and checkSessionThreshold.
    • Refactored GET, PATCH, and DELETE endpoints to use new action handlers and helper functions from ./helpers.ts.
    • Introduced authenticateAndResolveWorkspace helper for common authentication and workspace resolution logic.
    • Updated PATCH endpoint to use patchSessionBodySchema for validation and delegate actions to specific handlers.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/schemas.ts
    • Added new file schemas.ts defining Zod schemas and interfaces for ChainSummary, pauseActionSchema, editActionSchema, and patchSessionBodySchema.
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/threshold.ts
    • Added new file threshold.ts containing getSessionChainRoot and checkSessionThreshold helper functions for managing time tracking session thresholds.
  • packages/hooks/src/hooks/use-missed-entry-form.ts
    • Added uploadTimeTrackingImages import.
    • Updated useMissedEntryForm to use uploadTimeTrackingImages for image uploads and send image paths in the JSON body.
  • packages/hooks/src/upload-time-tracking-images.ts
    • Added new utility file upload-time-tracking-images.ts to handle image uploads to Supabase Storage via signed URLs.
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.

@vercel vercel Bot temporarily deployed to Preview – track February 24, 2026 08:31 Inactive
@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 4489 ✅ Complete
Errors 0 ✅ None
⚠️ Warnings 0 ✅ None
ℹ️ Info 0 ✅ None
📝 Total Issues 0 ✅ Passed
🔍 Lint Check Results
Metric Value Status
📁 Files Checked 4494 ✅ 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, 9:11:19 AM

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 86.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.99%. Comparing base (2a8ee02) to head (3cd9ff4).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
...time-tracking/sessions/[sessionId]/actions/edit.ts 86.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4344      +/-   ##
==========================================
- Coverage   49.11%   48.99%   -0.13%     
==========================================
  Files         256      257       +1     
  Lines       19633    19732      +99     
  Branches     6706     6777      +71     
==========================================
+ Hits         9643     9667      +24     
- Misses       8141     8184      +43     
- Partials     1849     1881      +32     

☔ 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 ready for review February 24, 2026 08:35
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 several changes to the time tracking feature, including new data models for period statistics and session pages, modifications to the time tracker repository to support fetching historical sessions and period stats, and UI enhancements in the shell page and time tracker page. The changes also include image uploading using signed URLs, and various bug fixes and improvements. Review comments suggest scoping the admin delete operation to also include the user_id and removing the unused permissions check, using the uuid package instead of manual implementation, and wrapping the dialog's content in a WillPopScope widget to block dismissal while _isSubmitting is true.

I am having trouble creating individual review comments. Click here to see my feedback.

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

high

The DELETE handler correctly verifies session ownership using the user's Supabase client. However, the actual deletion is performed with the admin client and is only scoped by sessionId and wsId, not the user ID. This could lead to accidental deletion if the ownership check logic were to have a bug. Additionally, permissions are fetched but not used, which is dead code.

To improve security and clarity, I recommend scoping the admin delete operation to also include the user_id and removing the unused permissions check. This ensures that only the verified owner's session can be deleted.

    const sbAdmin = await createAdminClient();
    const { error } = await sbAdmin
      .from('time_tracking_sessions')
      .delete()
      .match({ id: sessionId, ws_id: normalizedWsId, user_id: user.id });

apps/mobile/lib/data/repositories/time_tracker_repository.dart (234-251)

medium

While this manual implementation of _generateUuidV4 appears correct, it's generally better to use a standard, well-tested package for generating UUIDs to ensure correctness and reduce boilerplate code. Consider using the uuid package. You would need to add it to your pubspec.yaml and import it.

  String _generateUuidV4() {
    const uuid = Uuid();
    return uuid.v4();
  }

apps/mobile/lib/features/time_tracker/widgets/edit_session_dialog.dart (77-287)

medium

The dialog now handles async save operations, which is great. However, the user can still dismiss the dialog (e.g., by tapping the back button or scrim) while the save operation is in progress. This can lead to a confusing user experience. To prevent this, consider wrapping the dialog's content in a WillPopScope widget to block dismissal while _isSubmitting is true.

@coderabbitai coderabbitai Bot added enhancement New feature or request platform Infrastructure changes labels Feb 24, 2026
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: 4

Caution

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

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

249-332: ⚠️ Potential issue | 🟠 Major

Capture storage deletion errors before updating the database.
The remove(removedImages) call at line 303–305 ignores its error result. If deletion fails, the database is updated and the images are removed from the records while the files remain in storage, causing orphaned data. Also applies to the cleanup remove() call at line 329–331.

Example fix
-      await storageClient.storage
-        .from('time_tracking_requests')
-        .remove(removedImages);
+      const { error: removeError } = await storageClient.storage
+        .from('time_tracking_requests')
+        .remove(removedImages);
+      if (removeError) {
+        console.error('Failed to remove images:', removeError);
+        return NextResponse.json(
+          { error: 'Failed to remove images from storage' },
+          { status: 500 }
+        );
+      }

Apply the same pattern to the cleanup removal at line 329–331.

🤖 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/requests/[id]/route.ts
around lines 249 - 332, The storage deletion calls currently ignore their
results—capture and check the result of
storageClient.storage.from('time_tracking_requests').remove when deleting
removedImages before performing the DB update and return a 500 (or handle
appropriately) if the removal fails to avoid leaving orphaned files; likewise,
when cleaning up newly uploaded images after an update error (the
remove(newImagePaths) call guarded by updateError), also check the removal
result and surface/log/return an error if cleanup fails so failures are not
silently ignored. Locate the calls that reference removedImages/newImagePaths,
storageClient.storage.from('time_tracking_requests').remove, and the update
error handling around updateError to implement this change.
packages/hooks/src/hooks/use-missed-entry-form.ts (1)

365-474: ⚠️ Potential issue | 🟠 Major

Refactor mutations to use TanStack Query useMutation instead of raw fetch() calls.

This custom hook uses raw fetch() for mutations in createMissedEntry and handleDiscardSession functions. While the hook does call queryClient.invalidateQueries() afterward, this violates the mandatory requirement to use TanStack Query hooks for all client-side data fetching.

Wrap these mutations in useMutation() hooks and ensure fetch calls within queryFn include cache: 'no-store' to prevent dual-layer caching conflicts.

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

In `@packages/hooks/src/hooks/use-missed-entry-form.ts` around lines 365 - 474,
The createMissedEntry (and related handleDiscardSession) functions perform
POST/PATCH/DELETE via raw fetch() — refactor these mutation calls to use
TanStack Query's useMutation hooks instead: extract the network logic currently
inside createMissedEntry (the PATCH to sessions, DELETE session, image upload
POST, and the POST to /time-tracking/requests) into mutation queryFn(s) wired
with useMutation, call mutate/mutateAsync from
createMissedEntry/handleDiscardSession, and ensure each fetch in those queryFn
functions includes { cache: 'no-store' } in the request options; keep existing
error handling and call queryClient.invalidateQueries() in onSuccess/onSettled
of the mutations rather than after raw fetches.
apps/mobile/lib/features/time_tracker/cubit/time_tracker_requests_cubit.dart (1)

114-152: ⚠️ Potential issue | 🟠 Major

Rethrow updateRequest failures after emitting error state.

updateRequest swallows exceptions by returning null, which prevents callers from handling failures consistently. Emit the error state and rethrow to comply with the Cubit error-handling rule.

🔧 Suggested fix
     } on Exception catch (e) {
       emit(state.copyWith(error: e.toString()));
-      return null;
+      rethrow;
     }

As per coding guidelines: “apps/mobile/lib/**/*{cubit,bloc}.dart: If a Flutter Cubit catches repository errors, rethrow after emitting error state so callers can handle failures locally.”

🤖 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_requests_cubit.dart`
around lines 114 - 152, The updateRequest method currently catches exceptions,
emits an error state, and returns null which hides failures; modify the catch
block in updateRequest (the try around _repo.updateRequest and emit via
state.copyWith) so that after emitting the error state you rethrow the exception
(use rethrow to preserve stack) instead of returning null, ensuring callers can
handle failures while the Cubit still updates its state.
apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart (1)

303-370: ⚠️ Potential issue | 🟡 Minor

Capture root navigator before the first await.

Navigator.of(context, rootNavigator: true) is accessed after await widget.onSave(...). Capture it up front to avoid context reads after the first await.

🔧 Suggested fix
-                        final navigator = Navigator.of(context);
+                        final navigator = Navigator.of(context);
+                        final rootNavigator = Navigator.of(
+                          context,
+                          rootNavigator: true,
+                        );
                         setState(() => _isSubmitting = true);
...
-                          final toastContext = Navigator.of(
-                            context,
-                            rootNavigator: true,
-                          ).context;
+                          final toastContext = rootNavigator.context;
...
-                          final toastContext = Navigator.of(
-                            context,
-                            rootNavigator: true,
-                          ).context;
+                          final toastContext = rootNavigator.context;

As per coding guidelines: “Capture context dependencies before first await and guard BuildContext usage after await with if (!context.mounted) return; in Flutter.”

🤖 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/missed_entry_dialog.dart`
around lines 303 - 370, The code reads Navigator.of(context, rootNavigator:
true).context after awaiting widget.onSave which risks using a stale
BuildContext; capture that root navigator context before the first await and
reuse it afterwards. Specifically, before calling await widget.onSave(...)
compute a final variable (e.g. toastContext = Navigator.of(context,
rootNavigator: true).context) and use that variable in subsequent shad.showToast
calls instead of calling Navigator.of(...) again; keep existing checks for if
(!context.mounted) after the await and maintain setting _isSubmitting and
dialog/navigator behavior as-is.
🤖 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/test/data/repositories/time_tracker_repository_test.dart`:
- Around line 40-192: The temp directories created in the two tests
('createRequest uploads images first and sends storage paths' and 'updateRequest
uploads new images and sends JSON body') are deleted manually at the end and
won’t be removed if an assertion fails; after creating tempDir (the Directory
returned by Directory.systemTemp.createTemp) register a tear-down that deletes
it (use addTearDown(() async => await tempDir.delete(recursive: true))); remove
the manual await tempDir.delete(...) calls at the end of each test so cleanup
always runs even if repository.createRequest or repository.updateRequest
assertions fail.

In
`@apps/web/src/app/`[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts:
- Around line 216-277: The local uploadTimeTrackingImages function duplicates
existing logic in packages/hooks/src/upload-time-tracking-images.ts; remove this
local implementation and import the shared helper instead (ensure the helper is
exported from the hooks package index if it isn't already). Edit the module that
declares uploadTimeTrackingImages to import the shared function (same name or
alias) from the hooks package and replace all internal calls/usages with that
import; if the helper isn't exported from the package root, add a re-export in
the hooks package index so it can be consumed here.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/requests/upload-url/route.ts:
- Around line 16-33: The batch upload endpoint allows up to
MAX_FILES_PER_REQUEST (currently 10) which diverges from the create route limit
of 5 and can cause orphaned uploads; change MAX_FILES_PER_REQUEST to 5 and
ensure BatchUploadUrlRequestSchema (the files array validation) uses that
constant so the upload-url route enforces the same cap as the create route
(review SingleUploadUrlRequestSchema and UploadUrlFileSchema references to
confirm no other limits must be adjusted).

In `@packages/hooks/src/hooks/use-missed-entry-form.ts`:
- Line 8: The import in use-missed-entry-form.ts uses a deep relative path to
uploadTimeTrackingImages; re-export uploadTimeTrackingImages from the package
root index (add it to the package's src/index.ts exports) and update the import
in use-missed-entry-form.ts to import { uploadTimeTrackingImages } from the
package root (i.e., from the package name) instead of
'../upload-time-tracking-images' so it conforms to the package's public API
rule.

---

Outside diff comments:
In
`@apps/mobile/lib/features/time_tracker/cubit/time_tracker_requests_cubit.dart`:
- Around line 114-152: The updateRequest method currently catches exceptions,
emits an error state, and returns null which hides failures; modify the catch
block in updateRequest (the try around _repo.updateRequest and emit via
state.copyWith) so that after emitting the error state you rethrow the exception
(use rethrow to preserve stack) instead of returning null, ensuring callers can
handle failures while the Cubit still updates its state.

In `@apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart`:
- Around line 303-370: The code reads Navigator.of(context, rootNavigator:
true).context after awaiting widget.onSave which risks using a stale
BuildContext; capture that root navigator context before the first await and
reuse it afterwards. Specifically, before calling await widget.onSave(...)
compute a final variable (e.g. toastContext = Navigator.of(context,
rootNavigator: true).context) and use that variable in subsequent shad.showToast
calls instead of calling Navigator.of(...) again; keep existing checks for if
(!context.mounted) after the await and maintain setting _isSubmitting and
dialog/navigator behavior as-is.

In
`@apps/web/src/app/api/v1/workspaces/`[wsId]/time-tracking/requests/[id]/route.ts:
- Around line 249-332: The storage deletion calls currently ignore their
results—capture and check the result of
storageClient.storage.from('time_tracking_requests').remove when deleting
removedImages before performing the DB update and return a 500 (or handle
appropriately) if the removal fails to avoid leaving orphaned files; likewise,
when cleaning up newly uploaded images after an update error (the
remove(newImagePaths) call guarded by updateError), also check the removal
result and surface/log/return an error if cleanup fails so failures are not
silently ignored. Locate the calls that reference removedImages/newImagePaths,
storageClient.storage.from('time_tracking_requests').remove, and the update
error handling around updateError to implement this change.

In `@packages/hooks/src/hooks/use-missed-entry-form.ts`:
- Around line 365-474: The createMissedEntry (and related handleDiscardSession)
functions perform POST/PATCH/DELETE via raw fetch() — refactor these mutation
calls to use TanStack Query's useMutation hooks instead: extract the network
logic currently inside createMissedEntry (the PATCH to sessions, DELETE session,
image upload POST, and the POST to /time-tracking/requests) into mutation
queryFn(s) wired with useMutation, call mutate/mutateAsync from
createMissedEntry/handleDiscardSession, and ensure each fetch in those queryFn
functions includes { cache: 'no-store' } in the request options; keep existing
error handling and call queryClient.invalidateQueries() in onSuccess/onSettled
of the mutations rather than after raw fetches.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8ee02 and a520dbd.

⛔ 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/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_requests_cubit.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_requests_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_request_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/request_detail_sheet.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_tab.dart
  • apps/mobile/lib/l10n/arb/app_en.arb
  • apps/mobile/lib/l10n/arb/app_vi.arb
  • apps/mobile/test/data/repositories/time_tracker_repository_test.dart
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • packages/hooks/src/upload-time-tracking-images.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (20)
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_requests_cubit.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_request_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/request_detail_sheet.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_requests_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.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_requests_cubit.dart
  • apps/mobile/lib/features/time_tracker/widgets/timer_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/edit_request_dialog.dart
  • apps/mobile/lib/features/time_tracker/widgets/request_detail_sheet.dart
  • apps/mobile/lib/features/time_tracker/cubit/time_tracker_cubit.dart
  • apps/mobile/test/data/repositories/time_tracker_repository_test.dart
  • apps/mobile/lib/features/time_tracker/view/time_tracker_requests_page.dart
  • apps/mobile/lib/features/time_tracker/widgets/missed_entry_dialog.dart
  • apps/mobile/lib/data/repositories/time_tracker_repository.dart
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_requests_cubit.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:

  • packages/hooks/src/upload-time-tracking-images.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid deep relative imports; expose via package index.ts

Files:

  • packages/hooks/src/upload-time-tracking-images.ts
  • packages/hooks/src/hooks/use-missed-entry-form.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:

  • packages/hooks/src/upload-time-tracking-images.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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:

  • packages/hooks/src/upload-time-tracking-images.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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:

  • packages/hooks/src/upload-time-tracking-images.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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:

  • packages/hooks/src/upload-time-tracking-images.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • packages/hooks/src/hooks/use-missed-entry-form.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts
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_vi.arb
  • apps/mobile/lib/l10n/arb/app_en.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_vi.arb
  • apps/mobile/lib/l10n/arb/app_en.arb
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/data/repositories/time_tracker_repository_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/data/repositories/time_tracker_repository_test.dart
🧠 Learnings (10)
📚 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/timer_tab.dart
  • apps/mobile/lib/features/time_tracker/widgets/missed_entry_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/*/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/[id]/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/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_vi.arb
  • apps/mobile/lib/l10n/arb/app_en.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_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/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/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/upload-url/route.ts
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/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/requests/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/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/missed_entry_dialog.dart
🧬 Code graph analysis (4)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/[id]/route.ts (1)
packages/sdk/src/storage.ts (1)
  • request (807-927)
packages/hooks/src/hooks/use-missed-entry-form.ts (1)
packages/hooks/src/upload-time-tracking-images.ts (1)
  • uploadTimeTrackingImages (11-75)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/upload-url/route.ts (3)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts (1)
  • POST (49-189)
apps/web/src/lib/api-auth.ts (1)
  • withSessionAuth (174-300)
packages/supabase/src/next/server.ts (1)
  • createDynamicAdminClient (187-198)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts (3)
packages/utils/src/constants.ts (1)
  • MAX_NAME_LENGTH (48-48)
packages/sdk/src/storage.ts (1)
  • request (807-927)
packages/supabase/src/next/server.ts (1)
  • createDynamicClient (132-167)
🔇 Additional comments (19)
apps/mobile/lib/l10n/arb/app_en.arb (1)

262-265: LGTM — new localization keys are well-formed and consistent.

All four keys follow the existing timerXxx naming convention, have natural English copy, and require no placeholder annotations.

apps/mobile/lib/l10n/arb/app_vi.arb (2)

262-265: Vietnamese translations look accurate and natural — LGTM.


262-265: The generated Dart localization files already contain all the new keys from the ARB update. No regeneration is required.

Likely an incorrect or invalid review comment.

packages/hooks/src/upload-time-tracking-images.ts (1)

11-74: Signed-URL upload helper looks solid.
Clear validation and per-upload error handling make this utility reliable to reuse.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/requests/hooks/use-request-mutations.ts (1)

294-311: Signed-URL paths are correctly wired into the update payload.

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

44-147: Signed URL generation flow looks solid.

apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/requests/route.ts (2)

14-47: Schema + image path validation additions look good.


82-163: JSON-only input enforcement and signed-path handling are correctly applied.

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

33-61: Edit payload schema and path validation are well-structured.

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

193-328: Signed-URL upload helper is well-validated.
Response shape checks and per-upload error handling look solid.


627-780: Create/update payloads correctly use uploaded image paths.

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

566-586: Rename to imageLocalPaths is correctly propagated.

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

399-416: Rename propagation looks consistent.

The onEdit wiring correctly forwards newImageLocalPaths to the cubit update call.

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

27-37: onSave signature rename aligns with local-path flow.

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

299-343: Rename propagation looks consistent.

The MissedEntryDialog onSave wiring correctly forwards imageLocalPaths.

apps/mobile/lib/features/time_tracker/widgets/edit_request_dialog.dart (2)

19-27: Signature rename looks good.


157-167: Call-site updated correctly.

apps/mobile/lib/features/time_tracker/widgets/request_detail_sheet.dart (2)

41-49: Signature rename looks good.


332-353: Forwarding newImageLocalPaths is consistent.

Comment thread packages/hooks/src/hooks/use-missed-entry-form.ts Outdated
- Added validation for start and end times in edit actions.
- Refactored image upload logic to streamline request submissions.
- Updated test teardown to ensure temporary directories are cleaned up.
- Introduced new test cases for editing time tracking sessions.
- Adjusted maximum file upload limits for time tracking requests.
- Improved error handling for image removal and cleanup processes.
Base automatically changed from feat/time-tracker-history-mobile to main February 24, 2026 12:38
@VNOsST
Copy link
Copy Markdown
Collaborator Author

VNOsST commented Feb 24, 2026

ready for merge @vhpx , no further changes will be made bar requested ones

@vhpx vhpx merged commit 17c0252 into main Feb 24, 2026
51 checks passed
@vhpx vhpx deleted the feat/signed-url-requests branch February 24, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request platform Infrastructure changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants