Skip to content

Enhance UI/UX of time-tracker #3101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Jun 18, 2025
Merged

Enhance UI/UX of time-tracker #3101

merged 45 commits into from
Jun 18, 2025

Conversation

Adinorio
Copy link
Collaborator

@Adinorio Adinorio commented Jun 16, 2025

  • Add assignee information to tasks API with is_assigned_to_current_user flag

  • Implement smart task sorting: assigned tasks → priority → creation date

  • Add 'My Tasks' and 'Unassigned' filters with count badges

  • Add filter persistence using localStorage with workspace-specific keys

  • Enhance task cards with assignee avatars and assignment status

  • Add visual indicators for user-assigned tasks (blue styling)

  • Implement active filter chips with individual remove functionality

  • Add feature parity between sidebar and timer controls filtering"

  • Added new heatmaps:
    image

  1. Hybrid year + calendar
    image

  2. Calendar only
    image

  3. Compact cards
    image

  • Added new quick access controls
    image
  1. Continue last session
    image

  2. Next task
    image

  3. Analytics
    image

  • Added new time tracker options and functionalities
    image
  1. Stopwatch
    image
    image
    image

  2. Pomodoro
    image
    image

  3. Custom = Enhanced stopwatch + countdown
    image
    image
    image
    image

Summary by CodeRabbit

  • New Features

    • Introduced multi-mode timer controls with Pomodoro, Custom Timer, and enhanced Stopwatch, including persistent pause/resume and advanced notifications.
    • Added advanced session analytics: live focus scores, productivity insights, and AI-generated suggestions in time tracking and session history.
    • Enhanced activity heatmap with multiple visualization modes, onboarding tips, and persistent user preferences.
    • Improved task filtering with assignee filters, quick counts, and better contextual information.
  • Improvements

    • Unified and extended time tracking data models for richer analytics and better consistency across the dashboard.
    • Upgraded UI for time tracking actions, quick actions, and timer controls with contextual badges, productivity tips, and improved styling.
    • Added detailed assignee information and assignment status to workspace tasks.
  • Bug Fixes

    • Improved error handling and data validation in workspace task and time tracking API endpoints.
  • Chores

    • Refactored and centralized type definitions and utility functions for time tracking and task management.
    • Updated database schema and API to support session resume tracking and new analytics fields.

- Add assignee information to tasks API with is_assigned_to_current_user flag
- Implement smart task sorting: assigned tasks → priority → creation date
- Add 'My Tasks' and 'Unassigned' filters with count badges
- Add filter persistence using localStorage with workspace-specific keys
- Enhance task cards with assignee avatars and assignment status
- Add visual indicators for user-assigned tasks (blue styling)
- Implement active filter chips with individual remove functionality
- Add feature parity between sidebar and timer controls filtering"
Copy link

graphite-app bot commented Jun 16, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

Copy link
Contributor

coderabbitai bot commented Jun 16, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a comprehensive overhaul to the time tracking and task management features. It adds new timer modes (Pomodoro, Custom, enhanced Stopwatch), persistent pause/resume logic, richer analytics and focus scoring, advanced session filtering, and multiple new visualization modes for activity heatmaps. Types are centralized, API responses are enriched, and database schema is updated for session resumption tracking.

Changes

File(s) / Area Change Summary
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx Refactored allTasks construction to use ExtendedWorkspaceTask with consistent typing and nullability; imported type from shared module.
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx Removed local type declarations; imported ExtendedWorkspaceTask, SessionWithRelations, TimerStats from shared types. Updated button styling for consistency.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx Added flexible settings, four visualization modes (original, hybrid, calendar-only, compact cards), onboarding tips with persistence, dropdown controls, and enhanced UI/UX for heatmap analytics.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx Removed local TimerStats type; imported from shared types. No logic changes.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx Enhanced advanced filtering, analytics, and summary cards. Added focus score, productivity type, time of day, session quality filters, AI insights, and expanded stacked session UI. Updated imports and props for new analytics functions.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx Switched to importing TimerStats from shared types. No logic changes.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx Major update: added Pomodoro, Custom, enhanced Stopwatch timer modes; persistent pause/resume; new settings dialogs; break reminders; task filtering improvements; keyboard shortcuts; extended UI and timer logic.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx Changed TimeTrackerData import to type-only from shared types; simplified initialData construction.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts New file: Centralizes interfaces: TimerStats, SessionWithRelations, TimeTrackingGoal, ExtendedWorkspaceTask, TaskFilters, TaskSidebarFilters, TimeTrackerData.
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts New file: Adds task filtering, sorting, and assignee initials utilities; provides hooks for task counts.
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts Added TypeScript interfaces for task/assignee data; improved query parameter parsing; enriched task response with assignee details and assignment status; improved error handling.
apps/web/src/components/command/quick-actions.tsx Added peak time and analytics badges to quick actions; updated action descriptions for contextual relevance.
apps/web/src/components/command/quick-time-tracker.tsx Replaced simple session info with a live analytics panel: dynamic focus score, productivity tips, session type indicator, and progress bar.
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts PATCH "resume" action now sets was_resumed: true on new session creation.
packages/types/src/supabase.ts Added was_resumed boolean to time_tracking_sessions and analytics view; reordered and expanded function argument and return types for consistency and detail.
apps/db/supabase/migrations/20250618000000_add_was_resumed_field.sql New migration: Adds was_resumed column to time_tracking_sessions, creates filtered index, and updates analytics view to include the new field and existing session details.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TimerControls
    participant LocalStorage
    participant API
    participant UI

    User->>TimerControls: Select timer mode (Stopwatch/Pomodoro/Custom)
    TimerControls->>UI: Show timer mode UI and settings
    User->>TimerControls: Start timer
    TimerControls->>API: Create new session
    TimerControls->>UI: Display timer countdown/progress
    User->>TimerControls: Pause timer
    TimerControls->>LocalStorage: Save paused session state
    User->>TimerControls: Resume timer
    TimerControls->>LocalStorage: Restore paused session state
    TimerControls->>UI: Resume timer display
    User->>TimerControls: Stop timer
    TimerControls->>API: Finalize session
    TimerControls->>UI: Update analytics and summaries
Loading
sequenceDiagram
    participant User
    participant ActivityHeatmap
    participant LocalStorage

    User->>ActivityHeatmap: Open heatmap
    ActivityHeatmap->>LocalStorage: Load settings and onboarding state
    ActivityHeatmap->>User: Show selected view mode (original/hybrid/calendar/cards)
    User->>ActivityHeatmap: Change view mode or settings
    ActivityHeatmap->>LocalStorage: Save updated settings
    ActivityHeatmap->>User: Show onboarding tips if applicable
Loading

Possibly related PRs

  • Enhance UI/UX of time-tracker #3101: Modifies TasksSidebarContent to map tasks to ExtendedWorkspaceTask with consistent typing and nullability, directly related to similar changes in this PR.

Suggested labels

upskii

Poem

In the garden of time, the bunnies hop,
With Pomodoro beats and timers that pop.
Focus scores bloom, analytics shine,
Heatmaps and sessions, all by design.
Tasks now sorted, with badges so bright,
Pause, resume, and work through the night!
🕒🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/types/src/supabase.ts

Oops! Something went wrong! :(

ESLint: 9.29.0

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@typescript-eslint/parser' imported from /eslint.config.mjs
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:255:9)
at packageResolve (node:internal/modules/esm/resolve:767:81)
at moduleResolve (node:internal/modules/esm/resolve:853:18)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:801:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:725:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:708:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:309:38)
at #link (node:internal/modules/esm/module_job:201:49)


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27b13ea and 37b6d71.

📒 Files selected for processing (2)
  • apps/db/supabase/migrations/20250618000000_add_was_resumed_field.sql (1 hunks)
  • packages/types/src/supabase.ts (21 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Post Copyable Unit Tests in Comment
  • Commit Unit Tests in branch feat/time-tracker/ux-improvement-v2

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Jun 16, 2025

Codecov Report

Attention: Patch coverage is 0% with 3137 lines in your changes missing coverage. Please review.

Project coverage is 0.81%. Comparing base (4354759) to head (27b13ea).

Files with missing lines Patch % Lines
[...wsId]/time-tracker/components/activity-heatmap.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/3101?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2F%5Blocale%5D%2F%28dashboard%29%2F%5BwsId%5D%2Ftime-tracker%2Fcomponents%2Factivity-heatmap.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9bbG9jYWxlXS8oZGFzaGJvYXJkKS9bd3NJZF0vdGltZS10cmFja2VyL2NvbXBvbmVudHMvYWN0aXZpdHktaGVhdG1hcC50c3g=) 0.00% 1408 Missing ⚠️
...[wsId]/time-tracker/components/session-history.tsx 0.00% 1105 Missing ⚠️
.../[locale]/(dashboard)/[wsId]/time-tracker/utils.ts 0.00% 215 Missing and 1 partial ⚠️
.../web/src/components/command/quick-time-tracker.tsx 0.00% 140 Missing ⚠️
.../[locale]/(dashboard)/[wsId]/time-tracker/types.ts 0.00% 85 Missing and 1 partial ⚠️
...eb/src/app/api/v1/workspaces/[wsId]/tasks/route.ts 0.00% 80 Missing ⚠️
[...sId]/calendar/components/tasks-sidebar-content.tsx](https://app.codecov.io/gh/tutur3u/platform/pull/3101?src=pr&el=tree&filepath=apps%2Fweb%2Fsrc%2Fapp%2F%5Blocale%5D%2F%28dashboard%29%2F%5BwsId%5D%2Fcalendar%2Fcomponents%2Ftasks-sidebar-content.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=tutur3u#diff-YXBwcy93ZWIvc3JjL2FwcC9bbG9jYWxlXS8oZGFzaGJvYXJkKS9bd3NJZF0vY2FsZW5kYXIvY29tcG9uZW50cy90YXNrcy1zaWRlYmFyLWNvbnRlbnQudHN4) 0.00% 51 Missing ⚠️
apps/web/src/components/command/quick-actions.tsx 0.00% 36 Missing ⚠️
...board)/[wsId]/calendar/components/time-tracker.tsx 0.00% 8 Missing ⚠️
.../[locale]/(dashboard)/[wsId]/time-tracker/page.tsx 0.00% 2 Missing and 1 partial ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3101      +/-   ##
==========================================
- Coverage    0.82%    0.81%   -0.02%     
==========================================
  Files        2535     2537       +2     
  Lines      315886   321986    +6100     
  Branches     3036     3038       +2     
==========================================
  Hits         2621     2621              
- Misses     311103   317201    +6098     
- Partials     2162     2164       +2     

☔ 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.

Copy link

@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.

Summary of Changes

Hello @Adinorio, 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 tracker's task management UI/UX. It introduces assignee information into the task data, implements a smarter sorting mechanism that prioritizes assigned tasks, and adds new filtering capabilities for 'My Tasks' and 'Unassigned' tasks. The filters are now persistent, and the UI provides clearer visual cues for assigned tasks and active filters, improving usability and task discoverability.

Highlights

  • Assignee Information: Tasks API now includes assignee details and a flag (is_assigned_to_current_user) indicating if the current user is assigned.
  • Task Sorting: Implemented a new sorting logic for tasks: assigned tasks are prioritized first, followed by priority (highest first), and then creation date (newest first).
  • Assignee Filters: Added 'My Tasks' and 'Unassigned' quick filters to both the timer controls dropdown and the sidebar, including counts for each category.
  • Filter Persistence: Task filters (board, list, assignee) are now persisted in localStorage per workspace, so they are remembered between sessions.
  • Enhanced Task Cards: Task cards in both the timer controls dropdown and the sidebar now display assignee avatars and include visual indicators (blue styling, 'Assigned to you' badge) for tasks assigned to the current user.
  • Active Filter Display: Active filters (search, board, list, assignee) are now displayed as removable chips in the sidebar, with a 'Clear all' option.
  • Search Enhancement: Task search now includes the task description in addition to the name.
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

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 issue 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 is currently in preview and 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 to provide feedback.

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 configureGemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@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 enhances the time-tracker's UI/UX. My review highlights opportunities to improve maintainability by reducing code duplication, enhance type safety in API data handling, and potentially optimize performance. Addressing these points will make the codebase more robust.

Comment on lines 1405 to 1414
{(() => {
const myTasksCount = tasks.filter(
(task) => task.is_assigned_to_current_user
).length;
return myTasksCount > 0 ? (
<span className="ml-1 rounded-full bg-current px-1.5 py-0.5 text-[10px] text-white">
{myTasksCount}
</span>
) : null;
})()}

Choose a reason for hiding this comment

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

medium

The calculation for myTasksCount involves filtering the tasks array within an IIFE directly in the JSX. If the tasks array is large, these repeated filter operations could impact performance. Consider memoizing these counts using useMemo.

Adinorio added 2 commits June 16, 2025 15:22
- Add assignee information to tasks API endpoint
- Implement 'My Tasks' and 'Unassigned' quick filters with count badges
- Add filter state persistence using localStorage
- Enhance task sorting to prioritize user's assigned tasks
- Add visual indicators for assigned tasks (blue styling, badges)
- Display assignee avatars in task cards
- Ensure feature parity between sidebar and timer controls
- Fix TypeScript linter errors"
@Adinorio Adinorio self-assigned this Jun 16, 2025
Adinorio and others added 20 commits June 16, 2025 15:38
- hide dragging of tasks tab when viewing other's tracked time
 - Add comprehensive session analytics with focus score calculation
- Implement advanced filtering (duration, productivity, time-of-day, quality)
- Add real-time productivity insights and AI coaching suggestions
- Fix task assignment API errors by using direct Supabase operations
- Improve Quick Actions with live focus scores and smart timer suggestions
- Disable Continue Last Session card when timer is already running
- Add Next Task preview with priority-based smart selection
- Enhance session history with productivity type classification
- Update command center with real-time metrics and streak tracking"
- import, incorrect types, mismatch, and undefined errors
…-v2' into feat/time-tracker/ux-improvement-v2
…-v2' into feat/time-tracker/ux-improvement-v2
- Remove disabled state from dismiss button when external settings provided
- Add intelligent display logic based on user engagement and timing
- Implement localStorage persistence for dismiss preferences
- Add auto-hide functionality after 45 seconds for experienced users
- Include contextual tips that adapt to current heatmap view mode
- Add cross-component event system for settings synchronization
- Enhance UX with proper hover states, tooltips, and accessibility
- Fix TypeScript compilation errors in compact cards component
- Ensure tips reappear strategically (every 7 days or view mode changes)
- Prevent annoying repetition while maintaining helpful onboarding flow
Copy link
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: 10

🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (1)

8-17: params type is incorrect – breaks Next.js typing & causes unnecessary await
params is synchronously supplied by the framework, not a Promise. Declaring it as Promise<…> forces the awkward await params and may break type-checking / IDE hints.

-interface Props {
-  params: Promise<{
-    locale: string;
-    wsId: string;
-  }>;
-}
+interface Props {
+  params: {
+    locale: string;
+    wsId: string;
+  };
+}
 
 export default async function TimeTrackerPage({ params }: Props) {
-  const { wsId } = await params;
+  const { wsId } = params;
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

1-1507: Break down this large component into smaller, focused components

This component is over 1500 lines and handles multiple responsibilities. It should be decomposed into smaller, more manageable components.

Consider breaking this component into:

  1. ActivityHeatmap.tsx - Main container component (200-300 lines)
  2. HeatmapViews/ - Directory for view components:
    • OriginalGridView.tsx - GitHub-style grid view
    • HybridView.tsx - Combined year/month view
    • CalendarView.tsx - Monthly calendar
    • CompactCardsView.tsx - Card-based view
  3. HeatmapControls.tsx - Settings dropdown and legend
  4. OnboardingTips.tsx - Onboarding tips component
  5. useHeatmapSettings.ts - Custom hook for settings management
  6. useOnboardingState.ts - Custom hook for onboarding state

This would improve:

  • Maintainability and readability
  • Test coverage (easier to test smaller components)
  • Performance (better code splitting)
  • Team collaboration (less merge conflicts)

Would you like me to help create this refactored structure?

♻️ Duplicate comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

106-136: any usage & quadratic de-duplication hinder type-safety and scalability

  1. The transformation pipeline still relies on any (task: any, a: any, user: any).
    This repeats the concern raised in earlier reviews – consider defining a DbTaskRow interface to capture the row shape returned by Supabase and keep the mapper strictly typed.

  2. De-duplicating assignees with

self.findIndex((u) => u.id === user.id) === index

is O(n²). A single Set gives linear complexity and clearer intent:

- const seen = new Set<string>()
- const assignees = task.assignees
-   ?.map((t) => t.user)
-   ?.filter((u) => u?.id && !seen.has(u.id) && seen.add(u.id))
+ const seen = new Set<string>();
+ const assignees =
+   task.assignees
+     ?.map((t) => t.user)
+     ?.filter((u): u is { id: string } => {
+       if (!u?.id || seen.has(u.id)) return false;
+       seen.add(u.id);
+       return true;
+     }) ?? [];

This also removes the optional-chaining warning reported by Biome.

🧹 Nitpick comments (17)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (2)

4-19: Clarify time-unit semantics in TimerStats
The interface mixes generic number fields without indicating whether they represent seconds, minutes, or milliseconds. Consumers must read the implementation to guess. Explicitly documenting (or renaming) these fields avoids silent unit-conversion bugs.

 export interface TimerStats {
-  todayTime: number;
-  weekTime: number;
-  monthTime: number;
+  /** time in seconds */
+  todayTime: number;
+  /** time in seconds */
+  weekTime: number;
+  /** time in seconds */
+  monthTime: number;

55-68: Filters default to empty strings – consider null | undefined
Using empty strings to denote “no filter” forces downstream comparisons (if (filter.board) vs if (filter.board !== '')). Allowing undefined is more idiomatic and avoids hidden truthy / falsy traps.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (1)

34-44: Minor: redundant data mapping
rawData already matches TimeTrackerData; the spread could replace the manual copy:

-const initialData: TimeTrackerData = {
-  categories: rawData.categories,
-  runningSession: rawData.runningSession,
-  recentSessions: rawData.recentSessions,
-  goals: rawData.goals,
-  tasks: rawData.tasks,
-  stats: rawData.stats,
-};
+const initialData: TimeTrackerData = { ...rawData };
apps/web/src/components/command/quick-actions.tsx (1)

19-22: isPeakHour recalculates every render & uses hard-coded slots
While cheap, moving this logic to useMemo (or a util) avoids recomputation and improves testability. Config-driven peak-hour windows would also decouple UI from business rules.

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

93-101: Minor: redundant null-check – data is never null on successful Supabase calls

supabase.from(...).select(...).range(...) returns data as an empty array, not null, when no rows match.
The additional if (!data) branch is therefore unreachable and can be removed to simplify the control-flow.

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (1)

1117-1118: Style overrides defeat variant semantics; consider custom variant instead

Each of these buttons sets variant="outline" / "destructive" and then overrides background, border, and text classes directly.
The resulting DOM mixes two styling sources, increasing CSS specificity and maintenance cost. If the default variants are insufficient, exposing a dedicated “muted” or “accent-outline” variant in the design-system would keep styling declarative:

-<Button
-  className="w-full bg-muted hover:bg-muted/80 text-foreground border border-border hover:border-accent ..."
-  variant="outline"
->
+<Button variant="muted" className="w-full">

Also applies to: 1295-1296, 1962-1963

apps/web/src/components/command/quick-time-tracker.tsx (2)

380-393: Simplify progress bar color and width logic

The nested ternary operators for determining color classes and calculating width can be simplified using a mapping approach.

+const getProgressBarColor = (seconds: number): string => {
+  const colorMap = [
+    { threshold: 7200, color: "bg-green-500 dark:bg-green-600" },
+    { threshold: 3600, color: "bg-blue-500 dark:bg-blue-600" },
+    { threshold: 1800, color: "bg-yellow-500 dark:bg-yellow-600" },
+    { threshold: 0, color: "bg-gray-500 dark:bg-gray-600" }
+  ];
+  
+  return colorMap.find(({ threshold }) => seconds >= threshold)?.color || colorMap[colorMap.length - 1].color;
+};

 <div className={cn(
   "h-1.5 w-16 rounded-full",
-  elapsedTime >= 7200 ? "bg-green-500 dark:bg-green-600" :
-  elapsedTime >= 3600 ? "bg-blue-500 dark:bg-blue-600" :
-  elapsedTime >= 1800 ? "bg-yellow-500 dark:bg-yellow-600" : "bg-gray-500 dark:bg-gray-600"
+  getProgressBarColor(elapsedTime)
 )}>

429-446: Extract session type logic for better readability

The session type determination uses multiple nested ternary operators which reduces readability.

+interface SessionType {
+  icon: string;
+  label: string;
+  colorClasses: string;
+}
+
+const getSessionType = (elapsedSeconds: number): SessionType => {
+  const types: Array<{ threshold: number; type: SessionType }> = [
+    {
+      threshold: 3600,
+      type: {
+        icon: '🧠',
+        label: 'Deep Work',
+        colorClasses: "text-green-700 bg-green-100 dark:text-green-300 dark:bg-green-950/30"
+      }
+    },
+    {
+      threshold: 1800,
+      type: {
+        icon: '🎯',
+        label: 'Focused',
+        colorClasses: "text-blue-700 bg-blue-100 dark:text-blue-300 dark:bg-blue-950/30"
+      }
+    },
+    {
+      threshold: 900,
+      type: {
+        icon: '📋',
+        label: 'Standard',
+        colorClasses: "text-yellow-700 bg-yellow-100 dark:text-yellow-300 dark:bg-yellow-950/30"
+      }
+    },
+    {
+      threshold: 0,
+      type: {
+        icon: '⚡',
+        label: 'Quick Task',
+        colorClasses: "text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-gray-950/30"
+      }
+    }
+  ];
+  
+  const sessionType = types.find(({ threshold }) => elapsedSeconds >= threshold)?.type || types[types.length - 1].type;
+  return sessionType;
+};

+const sessionType = getSessionType(elapsedTime);
 <div className={cn(
   "flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium",
-  elapsedTime >= 3600 ? "text-green-700 bg-green-100 dark:text-green-300 dark:bg-green-950/30" :
-  elapsedTime >= 1800 ? "text-blue-700 bg-blue-100 dark:text-blue-300 dark:bg-blue-950/30" :
-  elapsedTime >= 900 ? "text-yellow-700 bg-yellow-100 dark:text-yellow-300 dark:bg-yellow-950/30" :
-  "text-gray-700 bg-gray-100 dark:text-gray-300 dark:bg-gray-950/30"
+  sessionType.colorClasses
 )}>
-  <span>
-    {elapsedTime >= 3600 ? '🧠' :
-     elapsedTime >= 1800 ? '🎯' :
-     elapsedTime >= 900 ? '📋' : '⚡'}
-  </span>
-  <span>
-    {elapsedTime >= 3600 ? 'Deep Work' :
-     elapsedTime >= 1800 ? 'Focused' :
-     elapsedTime >= 900 ? 'Standard' : 'Quick Task'}
-  </span>
+  <span>{sessionType.icon}</span>
+  <span>{sessionType.label}</span>
 </div>
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (3)

24-28: Consider null safety for priority values

The priority comparison assumes numeric values but doesn't validate them. Consider adding null/undefined checks.

 // Then sort by priority (higher priority first)
-const aPriority = a.priority || 0;
-const bPriority = b.priority || 0;
+const aPriority = Number(a.priority) || 0;
+const bPriority = Number(b.priority) || 0;
 if (aPriority !== bPriority) {
   return bPriority - aPriority;
 }

40-129: Consider consolidating filter functions to reduce duplication

The filterTasksForTimer and filterTasksForSidebar functions share similar filtering logic. Consider extracting common logic to reduce duplication.

+interface BaseFilters {
+  board: string;
+  list: string;
+  assignee: string;
+}
+
+function applyCommonFilters(
+  task: ExtendedWorkspaceTask,
+  searchQuery: string,
+  filters: BaseFilters
+): boolean {
+  // Search filter
+  const matchesSearch = !searchQuery || 
+    task.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
+    task.description?.toLowerCase().includes(searchQuery.toLowerCase());
+
+  if (!matchesSearch) return false;
+
+  // Board filter
+  if (filters.board && filters.board !== 'all' && task.board_name !== filters.board) {
+    return false;
+  }
+
+  // List filter
+  if (filters.list && filters.list !== 'all' && task.list_name !== filters.list) {
+    return false;
+  }
+
+  // Assignee filter
+  if (filters.assignee === 'mine') {
+    return task.is_assigned_to_current_user === true;
+  } else if (filters.assignee === 'unassigned') {
+    return !task.assignees || task.assignees.length === 0;
+  } else if (filters.assignee && filters.assignee !== 'all') {
+    return task.assignees?.some(assignee => assignee.id === filters.assignee) ?? false;
+  }
+
+  return true;
+}

 export function filterTasksForTimer(
   tasks: ExtendedWorkspaceTask[],
   searchQuery: string,
   filters: TaskFilters
 ): ExtendedWorkspaceTask[] {
   return tasks.filter((task) => {
-    const matchesSearch =
-      task.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
-      task.description?.toLowerCase().includes(searchQuery.toLowerCase());
+    if (!applyCommonFilters(task, searchQuery, filters)) return false;

     const matchesPriority =
       filters.priority === 'all' || String(task.priority) === filters.priority;

     const matchesStatus =
       filters.status === 'all' ||
       (task.completed ? 'completed' : 'active') === filters.status;

-    const matchesBoard =
-      filters.board === 'all' || task.board_name === filters.board;
-
-    const matchesList =
-      filters.list === 'all' || task.list_name === filters.list;
-
-    const matchesAssignee =
-      filters.assignee === 'all' ||
-      (filters.assignee === 'mine' && task.is_assigned_to_current_user) ||
-      (filters.assignee === 'unassigned' &&
-        (!task.assignees || task.assignees.length === 0));
-
-    return (
-      matchesSearch &&
-      matchesPriority &&
-      matchesStatus &&
-      matchesBoard &&
-      matchesList &&
-      matchesAssignee
-    );
+    return matchesPriority && matchesStatus;
   });
 }

180-184: Add more robust handling for initials generation

The current implementation might produce unexpected results for certain edge cases.

 export function generateAssigneeInitials(assignee: {
   display_name?: string;
   email?: string;
 }): string {
-  return (
-    assignee.display_name?.[0] ||
-    assignee.email?.[0] ||
-    '?'
-  ).toUpperCase();
+  const name = assignee.display_name?.trim();
+  const email = assignee.email?.trim();
+  
+  if (name) {
+    // Handle names with multiple parts (e.g., "John Doe" -> "JD")
+    const parts = name.split(/\s+/).filter(Boolean);
+    if (parts.length >= 2) {
+      return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
+    }
+    return name[0].toUpperCase();
+  }
+  
+  if (email) {
+    // Use part before @ for email
+    const username = email.split('@')[0];
+    return username[0].toUpperCase();
+  }
+  
+  return '?';
 }
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)

61-198: Simplify onboarding state logic

The onboarding state management is complex with nested conditions. Consider extracting this logic into a custom hook.

// useOnboardingState.ts
export function useOnboardingState(viewMode: string) {
  const DISMISSAL_DURATION_DAYS = 7;
  const NEW_USER_VIEW_THRESHOLD = 3;
  const EXPERIENCED_USER_REMINDER_DAYS = 14;
  
  const [state, setState] = useState<OnboardingState>(() => 
    loadOnboardingState()
  );
  
  const shouldShowTips = useMemo(() => {
    if (!state.showTips) return false;
    
    const isNewUser = state.viewCount < NEW_USER_VIEW_THRESHOLD;
    const viewModeChanged = state.lastViewMode !== viewMode;
    const shouldShowPeriodicReminder = checkPeriodicReminder(state);
    
    return isNewUser || viewModeChanged || shouldShowPeriodicReminder;
  }, [state, viewMode]);
  
  const dismissTips = useCallback(() => {
    const newState = {
      ...state,
      showTips: false,
      dismissedAt: new Date().toISOString()
    };
    setState(newState);
    saveOnboardingState(newState);
  }, [state]);
  
  // Track view mode changes
  useEffect(() => {
    if (viewMode !== state.lastViewMode) {
      incrementViewCount(viewMode);
    }
  }, [viewMode, state.lastViewMode]);
  
  return { shouldShowTips, dismissTips };
}

1-1507: Add comprehensive unit tests for this component

The static analysis shows that most of this component is not covered by tests. Given its complexity and importance, comprehensive testing is essential.

Would you like me to help generate a comprehensive test suite for this component? The tests should cover:

  1. Different view modes rendering correctly
  2. Onboarding state management
  3. User interactions (settings changes, navigation)
  4. Edge cases (empty data, invalid dates)
  5. Accessibility features

This would significantly improve code confidence and maintainability.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (3)

344-344: Improve badge display clarity and string handling.

Two minor improvements:

  1. The focus score lacks a unit indicator (line 344)
  2. String replacement only handles first occurrence (line 352)
-                <span>Focus {avgFocusScore}</span>
+                <span>Focus {avgFocusScore}%</span>
-                <span className="capitalize">{productivityType.replace('-', ' ')}</span>
+                <span className="capitalize">{productivityType.replaceAll('-', ' ')}</span>

Also applies to: 352-352


1316-1318: Consider accessibility improvements for emoji usage.

The filter options use emojis that might not be accessible to screen reader users. Consider adding aria-labels or using icon components instead.

Example improvement for one of the select items:

-                              <SelectItem value="short">🏃 Short (&lt; 30 min)</SelectItem>
+                              <SelectItem value="short" aria-label="Short sessions less than 30 minutes">🏃 Short (&lt; 30 min)</SelectItem>

Also applies to: 1341-1345, 1364-1367, 1386-1390, 1409-1412


763-2205: Consider decomposing this large component for better maintainability.

The SessionHistory component is over 1400 lines and handles many responsibilities. While the implementation is solid, consider extracting some parts into separate components:

  • Analytics calculation functions → separate utility module
  • Filter UI → FilterPanel component
  • Month view analytics cards → MonthViewAnalytics component
  • AI insights section → AIInsights component

This would improve:

  • Code organization and readability
  • Testability of individual parts
  • Performance through better memoization opportunities
  • Team collaboration on different features
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx (1)

1242-1244: Memoise getFilteredTasks to avoid O(N) work on every render

getFilteredTasks() is recomputed multiple times per render (keyboard navigation, counts, etc.).
Wrap it in useMemo with [tasks, taskSearchQuery, taskFilters] deps or move filtering into the shared util hook.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4354759 and 68bfc0b.

📒 Files selected for processing (13)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (4 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (16 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/timer-controls.tsx (30 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (3 hunks)
  • apps/web/src/components/command/quick-actions.tsx (4 hunks)
  • apps/web/src/components/command/quick-time-tracker.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • ExtendedWorkspaceTask (40-52)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (3)
  • ExtendedWorkspaceTask (40-52)
  • TaskFilters (55-61)
  • TaskSidebarFilters (64-68)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
packages/types/src/db.ts (3)
  • TimeTrackingSession (74-74)
  • TimeTrackingCategory (73-73)
  • WorkspaceTask (23-23)
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

[error] 129-130: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[error] 754-755: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 817-817: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 1083-1083: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 1087-1087: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 1095-1096: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx

[warning] 72-72: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L72
Added line #L72 was not covered by tests


[warning] 1117-1117: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1117
Added line #L1117 was not covered by tests


[warning] 1295-1295: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1295
Added line #L1295 was not covered by tests


[warning] 1962-1962: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1962
Added line #L1962 was not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx

[warning] 13-13: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L13
Added line #L13 was not covered by tests


[warning] 189-189: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L189
Added line #L189 was not covered by tests


[warning] 193-203: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L193-L203
Added lines #L193 - L203 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests


[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests


[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests


[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests


[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests


[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests


[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests


[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests


[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests


[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests


[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests


[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests


[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests


[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests


[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests


[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests


[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests


[warning] 551-1238: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1238
Added lines #L551 - L1238 were not covered by tests


[warning] 1240-1240: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1240
Added line #L1240 was not covered by tests


[warning] 1253-1348: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1253-L1348
Added lines #L1253 - L1348 were not covered by tests


[warning] 1353-1382: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1353-L1382
Added lines #L1353 - L1382 were not covered by tests


[warning] 1384-1393: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1384-L1393
Added lines #L1384 - L1393 were not covered by tests


[warning] 1395-1396: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1395-L1396
Added lines #L1395 - L1396 were not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
🔇 Additional comments (9)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)

40-52: Optional fields in ExtendedWorkspaceTask may mask data-integrity issues
Every extra property is declared optional, meaning undefined quietly propagates. If board_name, list_name, or assignee_name are always expected once tasks are enriched, mark them as required or document why they can be absent. This tightens type-safety and surfaces backend/transform bugs earlier.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx (1)

7-8: Good move centralising types
Importing TimerStats from the shared module removes duplication and keeps the component lean.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx (1)

3-4: Consistent shared-type usage acknowledged
Switching to TimerStats / TimeTrackingGoal from the central file improves maintainability.

apps/web/src/components/command/quick-actions.tsx (2)

66-72: Icon import safety
Brain is assumed to exist in lucide-react; if the package version lacks it CI will fail. Verify the icon is present or use a fallback.


97-108: Descriptive copy 👍
The analytics badge and updated description immediately convey added value to users.

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)

13-13: Good call centralising the type declaration

Importing ExtendedWorkspaceTask from the shared time-tracker/types module removes local duplication and keeps all task-related types in one place.

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (1)

72-72: 👍 Unified type imports

Replacing local interface declarations with the shared ones improves consistency across components and prevents divergence.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)

838-847: Use optional chaining for safer property access.

The current implementation could throw if task is undefined or null.

-      const task = tasks.find(t => t.id === session.task_id);
-      return task?.board_name || 'project-work';
+      const task = tasks.find(t => t.id === session.task_id);
+      return task?.board_name ?? 'project-work';

Likely an incorrect or invalid review comment.


275-282: Ensure dynamic Tailwind classes are preserved in production.

The dynamic class generation for productivity colors might get purged by Tailwind CSS in production builds. These classes need to be safelisted in your Tailwind configuration.

#!/bin/bash
# Check if Tailwind config includes safelist for these dynamic classes
fd -e js -e ts "tailwind.config" --exec grep -l "safelist" {} \; | head -5

# Also check for any existing safelist patterns
fd -e js -e ts "tailwind.config" --exec grep -A 10 -B 2 "safelist" {} \; | head -30

Adinorio added 2 commits June 18, 2025 10:34
- Add comprehensive session protection to prevent mode switching during active timers
- Implement separate break time tracking for each timer mode (stopwatch, Pomodoro, custom)
- Add visual lock indicators and error messages when attempting restricted actions
- Create mode-specific break state persistence with localStorage support
- Enhance session restoration to maintain state when switching between timer modes
- Add session protection for timer mode selector, settings access, and custom type switching
- Implement mode-aware break reminders with separate counters per timer type
- Prevent data corruption and integrity errors through isolated state management
- Add comprehensive validation across all user interaction points
- Maintain backward compatibility while enhancing data safety and user experience
Copy link
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: 0

🔭 Outside diff range comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

175-178: Avoid in-place sorting to prevent hidden side-effects

sessions.sort(...) mutates the original array that is also referenced elsewhere.
Clone first to keep the helper pure:

-  const sortedSessions = sessions.sort((a, b) =>
+  const sortedSessions = [...sessions].sort((a, b) =>
     dayjs(a.start_time).diff(dayjs(b.start_time))
   );
♻️ Duplicate comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

842-845: Magic string check repeated

Still relying on the literal 'resumed' inside session.description to detect interruptions. This fragility was highlighted in an earlier review; please move to an explicit was_resumed flag (or similar) on the session model.

🧹 Nitpick comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

1023-1026: Simplify & harden best-time-of-day calculation

Current reduce logic is hard to read and accesses timeOfDayBreakdown inside the callback on both operands. A clearer, O(n) approach:

const bestTimeOfDay = Object.entries(timeOfDayBreakdown)
  .sort(([, a], [, b]) => b - a)[0]?.[0] ?? 'morning';

This also guards against unexpected empty objects.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68bfc0b and c30b9f3.

📒 Files selected for processing (1)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (17 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 592-593: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)

- Replace 'any' types with proper TypeScript interfaces in task data transformation
- Add TaskAssigneeData, TaskListData, and RawTaskData interfaces for better type safety
- Improve type checking in assignee filtering and mapping logic

Addresses code review feedback about type safety in API data handling.
Copy link
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: 0

🔭 Outside diff range comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

40-44: Incorrect params typing – may break type-checking and IntelliSense

params in a Next.js route handler is a plain object, not a Promise.
Declaring it as Promise<{ wsId: string }> forces consumers to await an object, and propagates the wrong type to downstream code.

-export async function GET(
-  request: NextRequest,
-  { params }: { params: Promise<{ wsId: string }> }
+export async function GET(
+  request: NextRequest,
+  { params }: { params: { wsId: string } }
 )

Apply the same fix to the POST handler (lines 182-186).

🧹 Nitpick comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)

24-36: RawTaskData misses columns used later (deleted)

The query filters with .eq('deleted', false) (line 113) yet deleted is not part of RawTaskData.
Add the field so future refactors or narrowing by keyed access don’t introduce unsound code.

 interface RawTaskData {
   id: string;
   name: string;
   ...
   list_id: string;
   task_lists: TaskListData | null;
   assignees?: TaskAssigneeData[];
+  deleted?: boolean;
 }

158-167: Avoid shadowing the outer user and simplify de-duplication

The parameter name user inside the .filter callback hides the authenticated user captured in the outer scope, which is error-prone. Renaming also clears the static-analysis optional-chain warning.

-            task.assignees
-              ?.map((a: TaskAssigneeData) => a.user)
-              .filter(
-                (user, index: number, self) =>
-                  user &&
-                  user.id &&
-                  self.findIndex((u) => u?.id === user.id) === index
-              ) || [],
+            task.assignees
+              ?.map((a) => a.user)
+              .filter(
+                (assigneeUser, idx, arr) =>
+                  assigneeUser?.id &&
+                  arr.findIndex((u) => u?.id === assigneeUser.id) === idx
+              ) ?? [],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c30b9f3 and b9e712e.

📒 Files selected for processing (1)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (4 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

[error] 163-164: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

112-114: Verify Supabase path filter

The chained filter
eq('task_lists.workspace_boards.ws_id', wsId)
is three levels deep. Supabase’s PostgREST path filtering usually supports one implicit join (table!inner(column)).
If this path is not accepted the query will silently fail and return all tasks.

Please confirm in a test workspace; if unsupported you’ll need an explicit join (.eq('task_lists.workspace_boards.ws_id', wsId) → join or RPC).

Adinorio added 5 commits June 18, 2025 11:13
- Use refs to access elapsedTime and hasReachedTarget in interval callbacks to prevent recreation every second
- Remove frequently changing state from useEffect dependencies in timer intervals
- Update checkBreakReminders to use elapsedTimeRef instead of direct elapsedTime dependency
- Reduces CPU usage from ~60 effect recreations per minute to only when settings change

Fixes interval being recreated every tick which was defeating the purpose of setInterval and causing unnecessary renders.
- Add guard to check targetTime > 0 before division to prevent NaN
- Set progress bar width to 0% when targetTime is 0 (enhanced-stopwatch mode)
- Prevents CSS rendering issues when timer mode doesn't use countdown

Fixes progress bar breaking in enhanced-stopwatch mode where targetTime can be 0.
- Use singleton AudioContext with useRef instead of creating new instance per sound
- Add lazy initialization to only create AudioContext when needed
- Resume suspended context automatically for browser compatibility
- Add proper cleanup on component unmount to prevent memory leaks

Fixes "too many AudioContexts" browser error during long timer sessions.
…ssions

- Store only minimal session identifiers instead of full SessionWithRelations objects
- Add fetchSessionById function to re-fetch session details from server when needed
- Implement PausedSessionData interface with only non-sensitive data (sessionId, elapsed, pauseTime, timerMode)
- Add automatic cleanup for invalid/deleted sessions
- Maintain legacy key cleanup for smooth migration

Prevents PII exposure and localStorage quota issues while ensuring data freshness.
- Add was_resumed field to time_tracking_sessions table
- Update API to set field when resuming sessions
- Replace description parsing with reliable boolean check
- Add database index for analytics performance

Fixes fragile magic string dependency in focus score calculation
Copy link
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: 2

🧹 Nitpick comments (2)
apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql (1)

13-15: Redundant DROP VIEW before CREATE OR REPLACE

CREATE OR REPLACE VIEW already replaces the view atomically.
The preceding DROP VIEW IF EXISTS incurs an extra invalidation period and requires super-user rights on some systems. You can remove lines 13-14 without changing behaviour.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

589-597: Simplify prevSession lookup & use optional chaining

The ternary inside prevSession is identical on both branches, and the subsequent null-guard can be shortened with ?.. This makes the intent clearer and removes the Biome warning.

-const prevSession = actualIndex > 0 ? 
-  (showAllSessions ? stackedSession.sessions[index - 1] : stackedSession.sessions[index - 1]) : null;
-const gapInSeconds =
-  prevSession && prevSession.end_time
-    ? sessionStart.diff(
-        dayjs.utc(prevSession.end_time).tz(userTimezone),
-        'seconds'
-      )
-    : null;
+const prevSession =
+  actualIndex > 0 ? stackedSession.sessions[index - 1] : null;
+
+const gapInSeconds = prevSession?.end_time
+  ? sessionStart.diff(
+      dayjs.utc(prevSession.end_time).tz(userTimezone),
+      'seconds'
+    )
+  : null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9e712e and 0f5e39d.

📒 Files selected for processing (4)
  • apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (17 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (2 hunks)
  • packages/types/src/supabase.ts (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (5)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • SessionWithRelations (22-25)
packages/ui/src/components/ui/badge.tsx (1)
  • Badge (49-49)
packages/ui/src/components/ui/dropdown-menu.tsx (5)
  • DropdownMenu (241-241)
  • DropdownMenuTrigger (255-255)
  • DropdownMenuContent (243-243)
  • DropdownMenuItem (245-245)
  • DropdownMenuSeparator (250-250)
packages/ui/src/components/ui/popover.tsx (3)
  • PopoverTrigger (47-47)
  • PopoverContent (47-47)
  • Popover (47-47)
packages/ui/src/components/ui/select.tsx (5)
  • Select (170-170)
  • SelectTrigger (178-178)
  • SelectValue (179-179)
  • SelectContent (171-171)
  • SelectItem (173-173)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 592-593: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
🔇 Additional comments (3)
apps/web/src/app/api/v1/workspaces/[wsId]/time-tracking/sessions/[sessionId]/route.ts (1)

117-133: Validate session state before creating a “resumed” clone.

resume currently inserts a new running session even if the original session is still marked is_running = true.
This opens the door to multiple concurrent “running” sessions for the same user, which breaks downstream analytics and any uniqueness constraints you may have (e.g. “only one active session per user/workspace”).

A minimal guard fixes the issue:

-if (action === 'resume') {
+if (action === 'resume') {
+  // Safety-check: only allow resume on a stopped / paused session
+  if (session.is_running) {
+    return NextResponse.json(
+      { error: 'Cannot resume a session that is still running' },
+      { status: 400 }
+    );
+  }

Optionally, wrap the guard + insert in a transaction (RPC or Postgres function) to avoid race conditions in high-latency scenarios.
[ suggest_essential_refactor ]

packages/types/src/supabase.ts (2)

7128-7132: Heat-map analytics view keeps was_resumed in sync – LGTM

The new field is propagated to time_tracking_session_analytics.Row, keeping the view API consistent with time_tracking_sessions. No further issues spotted here.


3782-3836: To confirm whether the was_resumed column is declared NOT NULL DEFAULT false, let’s locate its definition in your SQL migrations:

#!/usr/bin/env bash
set -e

# Search all SQL files for the was_resumed column definition
rg "was_resumed" -n --glob "*.sql"

- Implemented filtering logic for session data to enhance heatmap accuracy.
- Updated heatmap component to visualize filtered session data effectively.
Copy link
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

🧹 Nitpick comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

666-681: Buttons missing explicit type="button"
The calendar navigation arrows are plain <button> elements inside a form-less component. In a future integration where the heatmap is placed inside a form, the default type="submit" will accidentally submit the form. Add type="button" explicitly:

-<Button variant="ghost" size="icon" ...>
+<Button variant="ghost" size="icon" type="button" ...>

(Same for the right-arrow.)

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

589-598: Unnecessary complexity & missing optional-chain in gap calculation

  1. const actualIndex = showAllSessions ? index : index is always equal to index.
  2. prevSession && prevSession.end_time can be simplified with optional chaining, silencing the Biome hint and improving readability.
-const actualIndex = showAllSessions ? index : index;
-const prevSession = actualIndex > 0 ? 
-  (showAllSessions ? stackedSession.sessions[index - 1] : stackedSession.sessions[index - 1]) : null;
-const gapInSeconds =
-  prevSession && prevSession.end_time
+const prevSession = index > 0 ? stackedSession.sessions[index - 1] : null;
+const gapInSeconds =
+  prevSession?.end_time
     ? sessionStart.diff(
         dayjs.utc(prevSession.end_time).tz(userTimezone),
         'seconds'
       )
     : null;

This keeps the logic identical while trimming three lines and removes a linter warning.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f5e39d and 6366c76.

📒 Files selected for processing (2)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (17 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests


[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests


[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests


[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests


[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests


[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests


[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests


[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests


[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests


[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests


[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests


[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests


[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests


[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests


[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests


[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests


[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests


[warning] 551-1306: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1306
Added lines #L551 - L1306 were not covered by tests


[warning] 1308-1308: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1308
Added line #L1308 was not covered by tests


[warning] 1321-1416: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1321-L1416
Added lines #L1321 - L1416 were not covered by tests


[warning] 1421-1450: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1421-L1450
Added lines #L1421 - L1450 were not covered by tests


[warning] 1452-1461: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1452-L1461
Added lines #L1452 - L1461 were not covered by tests


[warning] 1463-1464: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1463-L1464
Added lines #L1463 - L1464 were not covered by tests

🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 592-593: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

808-829: Streak calculation uses only active days – always produces full streaks
monthData.dates only contains days that already have activity, so the for-loop never encounters a “gap”, making longestStreak equal to activeDays. Either push all days (including inactive) into dates or iterate over the calendar range to obtain meaningful streaks.

Adinorio and others added 3 commits June 18, 2025 11:50
- added optional chaining, constants extraction, and data preservation

- **heatmap**: Replace explicit null checks with optional chaining operators
  - Simplify property access patterns in activity filtering logic
  - Enhance code readability and maintain runtime safety
  - Fix static analysis warnings for complexity/useOptionalChain

- **quick-timer**: Extract focus score magic numbers and simplify calculation
  - Add FOCUS_SCORE_CONSTANTS and SESSION_THRESHOLDS constants
  - Extract calculateFocusScore helper function for better maintainability
  - Replace complex inline IIFE with clean function call in JSX
  - Update all hardcoded time comparisons to use named constants

- **tasks-sidebar**: preserve assignee metadata when transforming tasks
  - Prevent overwriting existing assignee information with undefined values
  - Keep assignee_name, assignee_avatar, is_assigned_to_current_user, and assignees fields
  - Transform tasks to ExtendedWorkspaceTask while preserving existing metadata
  - Fix potential data loss when tasks already contain enriched assignee information

These changes improve maintainability, readability, and data integrity across
the time tracking and task management components.
- added with type safety, performance, and maintainability enhancements

- **types**: Add JSDoc comments to clarify time units in TimerStats interface
  - Document duration fields as seconds to prevent unit conversion bugs
  - Add time unit documentation to dailyActivity duration field

- **types**: Replace empty string defaults with null for better type safety
  - Update TaskFilters and TaskSidebarFilters to use string | null
  - Improve filter comparisons by avoiding truthy/falsy traps

- **time-tracker**: Simplify data mapping with spread operator
  - Replace manual property mapping with cleaner spread syntax
  - Reduce code duplication in TimeTrackerData initialization

- **quick-actions**: Add memoization to peak hour calculation
  - Extract peak hours configuration as constants
  - Use useMemo to prevent unnecessary recalculation on each render
  - Improve testability by separating config from logic

- **api**: Remove redundant null check in tasks route
  - Supabase returns empty array, not null, when no rows match
  - Simplify control flow by removing unreachable branch

- **utils**: Enhance task filtering and sorting utilities
  - Add proper null safety for priority values using Number() conversion
  - Consolidate filter logic to reduce duplication between timer and sidebar
  - Extract common filter interface and shared filtering function
  - Improve initials generation for multi-word names and edge cases

- **timer-controls**: Add memoization to expensive task operations
  - Memoize filtered tasks calculation to avoid O(N) work on every render
  - Memoize unique boards and lists extraction for better performance
  - Replace function calls with memoized values in task navigation

- **heatmap**: Replace explicit null checks with optional chaining operators
  - Simplify property access patterns in activity filtering logic
  - Enhance code readability and maintain runtime safety
  - Fix static analysis warnings for complexity/useOptionalChain

- **quick-timer**: Extract focus score magic numbers and simplify calculation
  - Add FOCUS_SCORE_CONSTANTS and SESSION_THRESHOLDS constants
  - Extract calculateFocusScore helper function for better maintainability
  - Replace complex inline calculation with clean function call
  - Update all hardcoded time comparisons to use named constants

Performance improvements: Memoized calculations reduce O(N) operations on every render
Type safety: Added null safety guards and proper type documentation
Code quality: Extracted constants, consolidated utilities, improved readability
Static analysis: Fixed optional chaining warnings and removed unreachable code

- Replace spread operator with explicit type-safe field mapping
- Convert undefined values to null for database field compatibility
- Add non-null assertions for required fields (id, name, list_id)
- Preserve existing assignee metadata from partial task objects
- Eliminate TypeScript errors in tasks sidebar content component
Copy link
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: 1

🔭 Outside diff range comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

71-76: Guard against NaN when parsing limit / offset
parseInt() returns NaN for non-numeric values, which then propagates into Math.min and subsequently into .range(...), yielding an invalid query range and a runtime 400 error from PostgREST.

-const limit = Math.min(
-  parseInt(url.searchParams.get('limit') || '100'),
-  200
-);
-const offset = parseInt(url.searchParams.get('offset') || '0');
+const parseNumber = (v: string | null, fallback: number) => {
+  const n = Number(v);
+  return Number.isFinite(n) && n >= 0 ? n : fallback;
+};
+
+const limit  = Math.min(parseNumber(url.searchParams.get('limit'), 100), 200);
+const offset = parseNumber(url.searchParams.get('offset'), 0);
♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)

562-570: Division-by-zero still yields Infinity, inflating intensity for empty months
Same issue flagged earlier: when monthActivity.length === 0, the expression evaluates to Infinity and getIntensity returns the maximum level.

-        intensity: getIntensity(totalDuration / monthActivity.length || 0)
+        intensity: getIntensity(
+          monthActivity.length > 0
+            ? totalDuration / monthActivity.length
+            : 0
+        )

749-807: processMonthlyData recomputes every render – use useMemo instead of useCallback
You memoise the function but not its (expensive) result. Since weeks is a new array each render, the callback identity changes, defeating the purpose. Switch to useMemo:

-const processMonthlyData = useCallback(() => { ... }, [weeks]);
+const monthlyData = useMemo(() => { ... }, [weeks]);

Then pass monthlyData to downstream consumers.


1272-1296: useState inside a plain function violates the Rules-of-Hooks
renderCompactCards() is invoked conditionally (&& renderCompactCards()); hooks inside it will throw an “Invalid hook call” at runtime. Extract to a capitalised component (e.g. <CompactCardsView />) or move the state up.

This was pointed out in the previous review but is still present.

🧹 Nitpick comments (1)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

154-161: Shadowing the outer user variable hides bugs and hurts readability
Inside .filter(...) the parameter is also named user, masking the authenticated user from the outer scope. It’s harmless today but a ticking bomb for future edits.

-              ?.map((a: TaskAssigneeData) => a.user)
-              .filter(
-                (user, index: number, self) =>
-                  user &&
-                  user.id &&
-                  self.findIndex((u) => u?.id === user.id) === index
-              ) || [],
+              ?.map((a: TaskAssigneeData) => a.user)
+              .filter(
+                (assignee, index, self) =>
+                  assignee?.id &&
+                  self.findIndex((u) => u?.id === assignee.id) === index
+              ) || [],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6366c76 and 7b39922.

📒 Files selected for processing (8)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (4 hunks)
  • apps/web/src/components/command/quick-actions.tsx (4 hunks)
  • apps/web/src/components/command/quick-time-tracker.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/page.tsx
  • apps/web/src/components/command/quick-actions.tsx
  • apps/web/src/components/command/quick-time-tracker.tsx
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
packages/ui/src/components/ui/tooltip.tsx (3)
  • Tooltip (60-60)
  • TooltipTrigger (60-60)
  • TooltipContent (60-60)
packages/ui/src/components/ui/dropdown-menu.tsx (7)
  • DropdownMenu (241-241)
  • DropdownMenuTrigger (255-255)
  • DropdownMenuContent (243-243)
  • DropdownMenuLabel (246-246)
  • DropdownMenuItem (245-245)
  • DropdownMenuSeparator (250-250)
  • DropdownMenuCheckboxItem (242-242)
packages/ui/src/components/ui/calendar.tsx (1)
  • Calendar (211-211)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • ExtendedWorkspaceTask (44-56)
🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 4-14: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L4-L14
Added lines #L4 - L14 were not covered by tests


[warning] 20-22: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L20-L22
Added lines #L20 - L22 were not covered by tests


[warning] 27-28: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27-L28
Added lines #L27 - L28 were not covered by tests


[warning] 38-46: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L38-L46
Added lines #L38 - L46 were not covered by tests


[warning] 52-52: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L52
Added line #L52 was not covered by tests


[warning] 54-210: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L54-L210
Added lines #L54 - L210 were not covered by tests


[warning] 374-374: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L374
Added line #L374 was not covered by tests


[warning] 401-401: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401
Added line #L401 was not covered by tests


[warning] 403-403: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L403
Added line #L403 was not covered by tests


[warning] 417-417: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L417
Added line #L417 was not covered by tests


[warning] 434-434: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L434
Added line #L434 was not covered by tests


[warning] 436-437: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L436-L437
Added lines #L436 - L437 were not covered by tests


[warning] 439-444: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L439-L444
Added lines #L439 - L444 were not covered by tests


[warning] 446-448: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L446-L448
Added lines #L446 - L448 were not covered by tests


[warning] 459-459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L459
Added line #L459 was not covered by tests


[warning] 461-462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L461-L462
Added lines #L461 - L462 were not covered by tests


[warning] 467-471: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L467-L471
Added lines #L467 - L471 were not covered by tests


[warning] 551-1306: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L551-L1306
Added lines #L551 - L1306 were not covered by tests


[warning] 1308-1308: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1308
Added line #L1308 was not covered by tests


[warning] 1321-1416: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1321-L1416
Added lines #L1321 - L1416 were not covered by tests


[warning] 1421-1450: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1421-L1450
Added lines #L1421 - L1450 were not covered by tests


[warning] 1452-1461: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1452-L1461
Added lines #L1452 - L1461 were not covered by tests


[warning] 1463-1464: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1463-L1464
Added lines #L1463 - L1464 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx

[warning] 13-13: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L13
Added line #L13 was not covered by tests


[warning] 189-189: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L189
Added line #L189 was not covered by tests


[warning] 193-231: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L193-L231
Added lines #L193 - L231 were not covered by tests

🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

[error] 158-159: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview

Copy link
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: 1

🔭 Outside diff range comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (1)

38-44: params should not be typed as a Promise

Next.js passes params synchronously. Typing it as Promise<{ wsId: string }> forces an unnecessary await, complicates the signature, and mis-documents the handler.

-export async function GET(
-  request: NextRequest,
-  { params }: { params: Promise<{ wsId: string }> }
-) {
-  const { wsId } = await params;
+export async function GET(
+  request: NextRequest,
+  { params }: { params: { wsId: string } }
+) {
+  const { wsId } = params;
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

1320-1359: CSV export is vulnerable to Excel CSV-injection

If a title or description starts with =, +, -, or @, opening the exported CSV in Excel will execute the text as a formula.
Prefix user-controlled cells with an apostrophe to neutralise them:

-  return [
-    startTime.format('YYYY-MM-DD'),
-    session.title,
-
-    session.description || '',
-  ];
+  const escape = (v: string) =>
+    /^[=\+\-@]/.test(v) ? `'${v}` : v;
+
+  return [
+    startTime.format('YYYY-MM-DD'),
+    escape(session.title),
+
+    escape(session.description || ''),
+  ];

Small change, big security win.

♻️ Duplicate comments (4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)

212-215: Boolean fields coerced to null again – breaks declared typing
Previous review already covered this: archived, completed, and deleted are boolean | undefined in WorkspaceTask; forcing them to null violates the contract and propagates extra null-checks downstream.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)

1434-1472: Hooks used inside an ordinary function – violates the Rules of Hooks

renderCompactCards is invoked like a helper (renderCompactCards()) yet declares useState inside it.
Because the function is called conditionally and not as a React component (), React will throw Invalid hook call in production.

Suggested fix – promote it to a proper component:

-const renderCompactCards = () => {
+function RenderCompactCards() {
   const monthlyData = processMonthlyData();
   ...
   return (
     <CompactCardsContainer
       ...
     />
   );
 }

and render it

-{settings.viewMode === 'compact-cards' && renderCompactCards()}
+{settings.viewMode === 'compact-cards' && <RenderCompactCards />}

Rename to start with a capital letter to satisfy React’s component detection.


604-609: Division-by-zero still yields Infinity → always highest intensity

totalDuration / monthActivity.length || 0 performs the division first, so when monthActivity.length is 0 the value is Infinity, which is truthy – the || 0 fallback never fires.
getIntensity(Infinity) always returns 4, painting completely inactive months as “high activity”.

-        intensity: getIntensity(totalDuration / monthActivity.length || 0),
+        intensity: getIntensity(
+          monthActivity.length > 0
+            ? totalDuration / monthActivity.length
+            : 0
+        ),

This mirrors the earlier feedback that was marked “addressed” but is still present.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

884-889: was_resumed still not guaranteed on the type – compiler will complain

SessionWithRelations is still accessed with session.was_resumed (line 885) but the field is not part of the original Prisma-generated model. Unless you have extended the type in ../types.ts, this will raise a TS error and the property will be undefined at runtime, giving every session the full 20-point consistency bonus.

-const consistencyBonus = session.was_resumed ? 0 : 20;
+// Guard against undefined until the API/type is updated
+const consistencyBonus = session.was_resumed === true ? 0 : 20;

If the backend now supplies the boolean, extend the type accordingly; otherwise keep the strict comparison.

🧹 Nitpick comments (5)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)

155-161: Simplify null-checks with optional chaining & tighten uniqueness filter

The current filter relies on a triple check (user && user.id && …). Using optional chaining is terser, and avoids the lint warning surfaced by Biome.

-  ?.map((a: TaskAssigneeData) => a.user)
-  .filter(
-    (user, index: number, self) =>
-      user &&
-      user.id &&
-      self.findIndex((u) => u?.id === user.id) === index
-  ) || [],
+  ?.map((a) => a.user)
+  .filter(
+    (user, idx, self) =>
+      !!user?.id && self.findIndex((u) => u?.id === user.id) === idx
+  ) ?? [],

112-114: Redundant post-filtering — DB query already scopes by workspace

eq('task_lists.workspace_boards.ws_id', wsId) makes the subsequent .filter() on line 135 redundant. Dropping the JS-side filter saves CPU and bandwidth.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

798-861: processMonthlyData recomputed every render – useMemo is a better fit

The heavy aggregation runs on every render because useCallback caches the function, not its result, and you call it immediately afterwards. Replace with useMemo (or compute once outside the component if data is static) to avoid unnecessary work:

-const processMonthlyData = useCallback(() => {
-  ...
-}, [weeks]);
+const monthlyData = useMemo(() => {
+  ...
+}, [weeks]);

Downstream calls (calculateMonthlyStats(monthlyData)) then accept the pre-computed object.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)

617-623: Redundant actualIndex logic

actualIndex is always equal to index – the ternary branches are identical.
This variable can be removed and the two subsequent references can just use index, which marginally simplifies the loop.


625-631: Prefer optional chaining over manual null checks

prevSession && prevSession.end_time can be simplified with an optional chain, matching the code-base style and satisfying the Biome lint hint.

-const gapInSeconds =
-  prevSession && prevSession.end_time
-    ? sessionStart.diff(
-        dayjs.utc(prevSession.end_time).tz(userTimezone),
-        'seconds'
-      )
-    : null;
+const gapInSeconds = prevSession?.end_time
+  ? sessionStart.diff(
+      dayjs.utc(prevSession.end_time).tz(userTimezone),
+      'seconds'
+    )
+  : null;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b39922 and 9e20921.

📒 Files selected for processing (11)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (4 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (18 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (4 hunks)
  • apps/web/src/components/command/quick-actions.tsx (4 hunks)
  • apps/web/src/components/command/quick-time-tracker.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/stats-overview.tsx
  • apps/web/src/components/command/quick-time-tracker.tsx
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/goal-manager.tsx
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
  • apps/web/src/components/command/quick-actions.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • ExtendedWorkspaceTask (48-60)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 625-626: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

[error] 158-159: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests


[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests


[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests


[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests


[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests


[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests


[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests


[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests


[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests


[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests


[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests


[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests


[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests


[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests


[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests


[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests


[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests


[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests


[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests


[warning] 583-1473: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1473
Added lines #L583 - L1473 were not covered by tests


[warning] 1475-1475: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1475
Added line #L1475 was not covered by tests


[warning] 1488-1520: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1488-L1520
Added lines #L1488 - L1520 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx

[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests


[warning] 10-10: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L10
Added line #L10 was not covered by tests


[warning] 186-186: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L186
Added line #L186 was not covered by tests


[warning] 190-231: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190-L231
Added lines #L190 - L231 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx

[warning] 4-8: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L4-L8
Added lines #L4 - L8 were not covered by tests


[warning] 1118-1118: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1118
Added line #L1118 was not covered by tests


[warning] 1296-1296: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1296
Added line #L1296 was not covered by tests


[warning] 1963-1963: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx#L1963
Added line #L1963 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
🔇 Additional comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/time-tracker.tsx (2)

1118-1125: Styling tweak looks good

The updated button classes unify light & dark themes without affecting behaviour.


1963-1966: Consistent button styling – LGTM

Same comment as above; keeps UX consistent across dialogs.

2. fix(types): resolve TaskSidebarFilters type compatibility issues
3. fix(time-tracker): remove unused variables and improve string access safety
Copy link
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: 1

🔭 Outside diff range comments (2)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)

70-79: NaN/negative limit & offset → runtime DB error

parseInt() returns NaN for non-numeric input, which then propagates into Math.min/range() and makes Supabase reject the query.
A malicious / accidental ?limit=foo will therefore 500 the route.

-const limit = Math.min(
-  parseInt(url.searchParams.get('limit') || '100'),
-  200
-);
-const offset = parseInt(url.searchParams.get('offset') || '0');
+const parsedLimit  = Number.parseInt(url.searchParams.get('limit')  ?? '', 10);
+const limit        = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, 200) : 100;
+
+const parsedOffset = Number.parseInt(url.searchParams.get('offset') ?? '', 10);
+const offset       = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;

This keeps the endpoint resilient against bad input.


79-113: eq('task_lists.workspace_boards.ws_id', …) is not valid for the JS query-builder

Deep-path filters of the form table1.table2.column only work in the REST endpoint syntax.
In the JS client, you need to:

  1. use an !inner join on workspace_boards, and
  2. filter directly on that alias, e.g.:
-  .eq('task_lists.workspace_boards.ws_id', wsId)
+  .eq('workspace_boards.ws_id', wsId)          // or push the filter into the inner join

As written, Postgres will complain that the column does not exist and the handler will 500.

♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)

1434-1449: Hooks used inside a plain function – violates the Rules-of-Hooks

renderCompactCards() is invoked as a regular function ({settings.viewMode === 'compact-cards' && renderCompactCards()}) but it contains a useState call (line 1438).
React hooks must run only inside React components (capitalised function invoked as JSX) or inside custom hooks, never in an arbitrary helper that may be called conditionally. This will trigger the “Invalid hook call” runtime error as soon as the compact-cards mode is rendered.

-const renderCompactCards = () => {
+function CompactCardsView() {
   const monthlyData = processMonthlyData();
   ...
   const [currentIndex, setCurrentIndex] = useState(0);
   ...
-  return (<CompactCardsContainer ... />);
+  return <CompactCardsContainer ... />;
 }

 ...

-{settings.viewMode === 'compact-cards' && renderCompactCards()}
+{settings.viewMode === 'compact-cards' && <CompactCardsView />}

Rename the helper to start with a capital letter and render it via JSX to satisfy the Rules-of-Hooks.


604-609: Division-by-zero still yields Infinity, causing false “high-activity” months

getIntensity(totalDuration / monthActivity.length || 0) tries to fall back to 0 but the || short-circuit never fires because Infinity is truthy. An empty monthActivity therefore gets intensity 4, painting completely idle months as dark green.

-        intensity: getIntensity(totalDuration / monthActivity.length || 0),
+        intensity: getIntensity(
+          monthActivity.length > 0
+            ? totalDuration / monthActivity.length
+            : 0,
+        ),

This mirrors the earlier bot comment that was marked “addressed” but is still present.


798-861: processMonthlyData memoisation ineffective – useCallback around a function, not the result

weeks is recreated every render, so the useCallback dependency changes each time, producing a fresh function and defeating memoisation. More importantly, the heavy computation is re-executed again via processMonthlyData().

Prefer caching the computed data with useMemo:

-const processMonthlyData = useCallback(() => {
-  return weeks.reduce( ... )
-}, [weeks]);
+const monthlyData = useMemo(() => {
+  return weeks.reduce( ... );
+}, [weeks]);

Downstream callers (calculateMonthlyStats, card builders, etc.) can then consume monthlyData directly.

🧹 Nitpick comments (4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (2)

895-909: Trend calculation may yield NaN / Infinity when previous month is empty

When prevAvg === 0 the % computation divides by zero:

trendValue = ((currentAvg - prevAvg) / Math.max(prevAvg, 1)) * 100;

Math.max(prevAvg, 1) avoids Infinity, but trend above is still decided by comparing against prevAvg * 1.1 / 0.9, both zero, so any non-zero currentAvg is considered “up” by > 90 %. Consider treating a zero-activity previous month as neutral.

if (previousMonth && prevAvg > 0) {
  ...
} else {
  trend = 'neutral';
  trendValue = 0;
}

Prevents misleading “↗ ∞%” spikes.


1554-1563: Checkbox toggles only between ‘smart’ and ‘relative’, silently losing ‘absolute’

The “Show smart time references” checkbox sets timeReference to 'smart' or 'relative', but the settings type supports 'absolute' as well. If a user previously selected 'absolute', this toggle will overwrite it, making it impossible to return without a full settings reset.

Consider replacing the single checkbox with a radio-group or preserving the previous non-smart choice.

apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (2)

4-36: Centralise DTO types and keep them in sync with the SELECT list

The three interfaces are very useful, but duplicating them inside every route quickly drifts from reality.
We already have ExtendedWorkspaceTask in a shared types.ts; these “raw DB” DTOs should live next to it (or be generated with Zod) and be imported here, rather than re-declared.

While moving them, please also add the two fields that are currently missing from the type surface you use later:

  • board_id on TaskListData (line 96 of the SELECT).
  • deleted on RawTaskData (line 113 filter).

That way TypeScript will catch any divergence the next time the schema evolves.


127-131: Avoid re-logging & re-throwing raw DB errors

console.error + throw here is immediately followed by another catch that does the same.
Consider wrapping the error (throw new Error('TASKS_QUERY_FAILED')) or returning a typed Supabase error to prevent duplicate stack traces and accidental leakage of DB internals in production logs.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e20921 and 6eb032d.

📒 Files selected for processing (4)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts
🧰 Additional context used
🪛 Biome (1.9.4)
apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts

[error] 158-159: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Run tests and collect coverage
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview

Copy link
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

🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)

175-178: Avoid mutating the original sessions array when sorting

Array.prototype.sort mutates in-place. Because groupSessions is a reference stored in groups, any later consumer of that array will now receive the sorted version, which may produce subtle side-effects if order was expected to be stable. Clone before sorting:

-const sortedSessions = groupSessions.sort((a, b) =>
-  dayjs(a.start_time).diff(dayjs(b.start_time))
-);
+const sortedSessions = [...groupSessions].sort((a, b) =>
+  dayjs(a.start_time).diff(dayjs(b.start_time))
+);

1325-1346: Rename helper and fully escape CSV values to avoid clobbering the global escape and malformed CSV output

  1. Shadowing the global escape function triggers the Biome warning and is confusing.
  2. Wrapping every field in double quotes without doubling inner " produces invalid CSV when values themselves contain quotes.
  3. The current helper only prefixes ' for formula-injection, but ignores commas / newlines.
-const escape = (v: string) =>
-  /^[=+\-@]/.test(v) ? `'${v}` : v;
+const toSafeCsv = (v: string) => {
+  // Excel/Sheets formula injection guard
+  const safe = /^[=+\-@]/.test(v) ? `'${v}` : v;
+  // Escape double quotes by doubling them
+  return safe.replace(/"/g, '""');
+};

and later:

-escape(session.title),
-escape(session.category?.name || ''),
-escape(session.task?.name || ''),
+toSafeCsv(session.title),
+toSafeCsv(session.category?.name || ''),
+toSafeCsv(session.task?.name || ''),-escape(session.description || ''),
+toSafeCsv(session.description || ''),
♻️ Duplicate comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)

218-222: Boolean undefinednull coercion re-introduced
This exact issue was pointed out in an earlier review: archived, completed, and deleted are typed as boolean | undefined. Converting them to null violates the type contract and forces extra null checks everywhere else.

Please keep the original value (true/false/undefined) or provide an explicit boolean.

🧹 Nitpick comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

801-865: In-place sort mutates memoised state

calculateMonthlyStats sorts monthData.dates in-place. Because monthlyData is memoised, subsequent renders receive an already-sorted array, but the sort runs again and still mutates it. Not harmful today, yet it violates the expectation that memoised data is immutable.

Consider copying before sorting:

const sortedDates = [...monthData.dates].sort(
  (a, b) => a.date.valueOf() - b.date.valueOf()
);

Keeps the helper pure and future-proof.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

929-941: getProjectContext performs an O(n) search for every session – cache or pre-index

For large sessions × tasks sets this becomes quadratic. Build a Map<taskId, Task> once (e.g. inside a useMemo) and do O(1) look-ups inside the filter pipeline.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6eb032d and db073d4.

📒 Files selected for processing (5)
  • apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql (1 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (20 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
  • apps/db/supabase/migrations/20250119000000_add_was_resumed_field.sql
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • ExtendedWorkspaceTask (48-60)
packages/types/src/db.ts (1)
  • WorkspaceTask (23-23)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 1325-1326: Do not shadow the global "escape" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests


[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests


[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests


[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests


[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests


[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests


[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests


[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests


[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests


[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests


[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests


[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests


[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests


[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests


[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests


[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests


[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests


[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests


[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests


[warning] 583-1460: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1460
Added lines #L583 - L1460 were not covered by tests


[warning] 1462-1462: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1462
Added line #L1462 was not covered by tests


[warning] 1475-1507: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1475-L1507
Added lines #L1475 - L1507 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx

[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests


[warning] 10-10: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L10
Added line #L10 was not covered by tests


[warning] 186-186: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L186
Added line #L186 was not covered by tests


[warning] 190-238: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190-L238
Added lines #L190 - L238 were not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Prettier Check (23)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

598-607: Index mismatch when showAllSessions is false could mis-compute gaps

prevSession is taken from stackedSession.sessions[index - 1], but index is the position inside visibleSessions. When only the first INITIAL_SESSION_LIMIT items are shown, index still matches – but once you toggle to “show more” and then collapse again, the mapping can be off and yield negative/incorrect gaps. Consider deriving prevSession from visibleSessions instead:

const prevSession = index > 0 ? visibleSessions[index - 1] : null;

Copy link
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: 0

🔭 Outside diff range comments (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (2)

1327-1341: Rename helper to avoid shadowing global escape.

The locally-defined escape function collides with the (deprecated but still present) global window.escape, which static-analysis already warns about. While not fatal, shadowing well-known globals is confusing and can lead to subtle bugs if the global is ever referenced in the same scope (e.g. in browser dev-tools).

-const escape = (v: string) => (/^[=+\-@]/.test(v) ? `'${v}` : v);
+const csvEscape = (v: string) => (/^[=+\-@]/.test(v) ? `'${v}` : v);-  return [
-    startTime.format('YYYY-MM-DD'),
-    escape(session.title),
-    escape(session.category?.name || ''),
-    escape(session.task?.name || ''),
+  return [
+    startTime.format('YYYY-MM-DD'),
+    csvEscape(session.title),
+    csvEscape(session.category?.name || ''),
+    csvEscape(session.task?.name || ''),-    escape(session.description || ''),
+    csvEscape(session.description || ''),
   ];

175-177: Avoid mutating the original sessions array when sorting.

Array.prototype.sort mutates its target in-place.
Because createStackedSession receives the same array reference held elsewhere (e.g. groups[groupKey]), this silently re-orders the source collection and can cause hard-to-trace side-effects if any caller still relies on the original order.

-const sortedSessions = sessions.sort((a, b) =>
+const sortedSessions = [...sessions].sort((a, b) =>
   dayjs(a.start_time).diff(dayjs(b.start_time))
 );
♻️ Duplicate comments (3)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)

222-226: Stop coercing booleans to null
The same issue raised earlier is still present: converting undefinednull for archived, completed, deleted breaks the declared union boolean | undefined. Keep them as-is (task.archived, etc.) so consumers don’t have to handle a third state.


208-237: Object reconstruction still drops fields & violates boolean typings

  1. Re-creating the object field-by-field means every property you forget today (e.g. status, labels, custom columns) silently disappears downstream – the exact problem flagged in the previous review.
  2. archived | completed | deleted are boolean | undefined in WorkspaceTask; coercing them to null breaks the type contract and forces extra null checks elsewhere.

Prefer spreading the original task first, then override / extend only what’s really new:

-const extendedTask: ExtendedWorkspaceTask = {
-  // Required fields
-  id: task.id!,
-  name: task.name!,
-  ...
-  archived: task.archived ?? null,
-  completed: task.completed ?? null,
-  deleted: task.deleted ?? null,
+const extendedTask: ExtendedWorkspaceTask = {
+  ...task,                // retain *all* existing properties & correct types
+  board_name: board.name,
+  list_name: list.name,
+  // Preserve assignee metadata
+  assignee_name: taskMeta.assignee_name,
+  assignee_avatar: taskMeta.assignee_avatar,
+  is_assigned_to_current_user: taskMeta.is_assigned_to_current_user,
+  assignees: taskMeta.assignees,
 };

This also removes the need for the ! non-null assertions on core fields.

apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (1)

1101-1132: Mini-heatmap still hard-codes 4 weeks → truncates long months
Rendering Array.from({ length: 7 * 4 }) omits days 29-31 for most months – the exact problem highlighted in the previous review.

- {Array.from({ length: 7 * 4 }, (_, i) => {
+ {Array.from({ length: 7 * 6 }, (_, i) => {

…and adjust the iteration to stop when currentDay.month() !== monthStart.month() so you don’t leak next-month cells.

🧹 Nitpick comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (1)

195-201: Interface declaration inside loop – hoist to avoid redundant allocations
TaskWithAssigneeMeta is re-created on every iteration of list.tasks.map.
Hoist the interface (or a type alias) to file scope so the compiler emits it only once and runtime memory isn’t wasted.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db073d4 and 27b13ea.

📒 Files selected for processing (5)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (9 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (20 hunks)
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts (1 hunks)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/api/v1/workspaces/[wsId]/tasks/route.ts
  • apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/utils.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx (3)
packages/ui/src/components/ui/tooltip.tsx (3)
  • Tooltip (60-60)
  • TooltipTrigger (60-60)
  • TooltipContent (60-60)
packages/ui/src/components/ui/dropdown-menu.tsx (7)
  • DropdownMenu (241-241)
  • DropdownMenuTrigger (255-255)
  • DropdownMenuContent (243-243)
  • DropdownMenuLabel (246-246)
  • DropdownMenuItem (245-245)
  • DropdownMenuSeparator (250-250)
  • DropdownMenuCheckboxItem (242-242)
packages/ui/src/components/ui/calendar.tsx (1)
  • Calendar (211-211)
apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx (2)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/types.ts (1)
  • ExtendedWorkspaceTask (48-60)
packages/types/src/db.ts (1)
  • WorkspaceTask (23-23)
🪛 Biome (1.9.4)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx

[error] 1327-1327: Do not shadow the global "escape" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🪛 GitHub Check: codecov/patch
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx

[warning] 3-21: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L3-L21
Added lines #L3 - L21 were not covered by tests


[warning] 25-25: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L25
Added line #L25 was not covered by tests


[warning] 27-27: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L27
Added line #L27 was not covered by tests


[warning] 30-30: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L30
Added line #L30 was not covered by tests


[warning] 35-36: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L35-L36
Added lines #L35 - L36 were not covered by tests


[warning] 46-58: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L46-L58
Added lines #L46 - L58 were not covered by tests


[warning] 64-64: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L64
Added line #L64 was not covered by tests


[warning] 66-237: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L66-L237
Added lines #L66 - L237 were not covered by tests


[warning] 401-406: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L401-L406
Added lines #L401 - L406 were not covered by tests


[warning] 433-433: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L433
Added line #L433 was not covered by tests


[warning] 435-435: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L435
Added line #L435 was not covered by tests


[warning] 449-449: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L449
Added line #L449 was not covered by tests


[warning] 466-466: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L466
Added line #L466 was not covered by tests


[warning] 468-469: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L468-L469
Added lines #L468 - L469 were not covered by tests


[warning] 471-476: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L471-L476
Added lines #L471 - L476 were not covered by tests


[warning] 478-480: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L478-L480
Added lines #L478 - L480 were not covered by tests


[warning] 491-491: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L491
Added line #L491 was not covered by tests


[warning] 493-494: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L493-L494
Added lines #L493 - L494 were not covered by tests


[warning] 499-503: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L499-L503
Added lines #L499 - L503 were not covered by tests


[warning] 583-1457: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L583-L1457
Added lines #L583 - L1457 were not covered by tests


[warning] 1459-1459: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1459
Added line #L1459 was not covered by tests


[warning] 1472-1504: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1472-L1504
Added lines #L1472 - L1504 were not covered by tests


[warning] 1506-1610: apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/activity-heatmap.tsx#L1506-L1610
Added lines #L1506 - L1610 were not covered by tests

apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx

[warning] 5-5: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L5
Added line #L5 was not covered by tests


[warning] 190-190: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L190
Added line #L190 was not covered by tests


[warning] 194-242: apps/web/src/app/[locale]/(dashboard)/[wsId]/calendar/components/tasks-sidebar-content.tsx#L194-L242
Added lines #L194 - L242 were not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Deploy-Preview
  • GitHub Check: Verify generated types
  • GitHub Check: Deploy-Preview
🔇 Additional comments (1)
apps/web/src/app/[locale]/(dashboard)/[wsId]/time-tracker/components/session-history.tsx (1)

881-885: Confirm was_resumed is part of SessionWithRelations.

calculateFocusScore accesses session.was_resumed, but that property was previously missing in the public type and triggered CI errors (#prior review).
If the type definition in ../types has not been updated accordingly this line will still break the build.

Please verify that SessionWithRelations now includes:

interface SessionWithRelations {
  
  was_resumed?: boolean;
}

and that the backend populates it.
Otherwise add a runtime guard or extend the local type alias.

@vhpx vhpx merged commit ada832f into main Jun 18, 2025
15 of 16 checks passed
@vhpx vhpx deleted the feat/time-tracker/ux-improvement-v2 branch June 18, 2025 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants