Skip to content

Conversation

@samejr
Copy link
Member

@samejr samejr commented Jan 9, 2026

UI/UX improvement to the date/time picker:

  • You can now choose a custom duration
  • Adds a new DateTimePicker.tsx component, using a new shadcn Calendar.tsx component
  • Clear UI separation between the 2 actions, choosing a duration or choosing date range
  • Adds new quick select options for picking a date range quickly
DatePickerV2.mp4

claude and others added 28 commits January 8, 2026 13:21
Allow users to specify custom time intervals (e.g., 90 minutes) instead
of being limited to preset values. Adds a numeric input field with a
unit dropdown (minutes/hours/days) that works with the existing
parse-duration backend.

Slack thread: https://triggerdotdev.slack.com/archives/C032WA2S43F/p1767877958977499?thread_ts=1767877851.170929&cid=C032WA2S43F
Add useEffect to update customValue and customUnit when the period prop
changes, fixing stale values in the custom input fields after selecting
a preset time period.

Co-authored-by: Eric Allam <[email protected]>
Replace raw HTML input and select with the existing Input and SimpleSelect
primitives to match the app's design system aesthetic.

Co-authored-by: Eric Allam <[email protected]>
@changeset-bot
Copy link

changeset-bot bot commented Jan 9, 2026

⚠️ No Changeset found

Latest commit: 1dd70d1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Walkthrough

Adds a client-side Calendar component built on react-day-picker with a CustomMonthCaption (month/year selects and navigation) and exports Calendar and CalendarProps. Adds DateTimePicker, a popover control combining the Calendar with a time input and optional Now/Clear actions. Extends SharedFilters (runs/v3) with a two-section time filter UI (duration presets and custom duration; date-range with presets), validation, parsing helpers, QuickDateButton, and signature changes to TimeDropdown/timeFilters/timeFilterRenderValues; adjusts DateField imports/types. Adds dependencies date-fns and react-day-picker.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides a clear overview of improvements and lists key features, but does not follow the repository's template structure with required sections like Testing, Changelog, and the Checklist. Add missing template sections: include the Checklist, Testing steps, and a formatted Changelog section to follow the repository's PR description conventions.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: upgrading the dateTime filter UI with new features for custom durations and date range selection.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

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

🤖 Fix all issues with AI agents
In @apps/webapp/app/components/runs/v3/SharedFilters.tsx:
- Around line 345-353: The useEffect that initializes custom duration and
selectedPeriod uses defaultPeriod when computing setSelectedPeriod but doesn't
include defaultPeriod in its dependency array; update the dependency array of
the useEffect containing getInitialCustomDuration, setCustomValue, setCustomUnit
and setSelectedPeriod to include defaultPeriod so the effect re-runs and
selectedPeriod updates if defaultPeriod changes.
- Around line 728-740: The Cancel button onClick currently only restores from/to
and validation state; also reset duration-related state so changes don't
persist: in the Cancel handler (the Button onClick) call
setSelectedPeriod(period), setCustomValue(customValue),
setCustomUnit(customUnit) to restore the original duration props/values and
setActiveSection(null) (or the component's default section) in addition to
setFromValue(from), setToValue(to), setValidationError(null), and
setOpen(false).
🧹 Nitpick comments (4)
apps/webapp/app/components/primitives/Calendar.tsx (1)

53-60: Consider making the year range configurable or expanding it.

The year dropdown shows 100 years centered on the current year (±50 years). This is reasonable for most use cases, but for applications requiring historical data (e.g., birth dates) or future planning, this range might be limiting.

♻️ Optional: Make year range configurable
+type CustomMonthCaptionProps = {
+  calendarMonth: { date: Date };
+  yearRange?: { start: number; end: number };
+};

-function CustomMonthCaption({ calendarMonth }: { calendarMonth: { date: Date } }) {
+function CustomMonthCaption({ calendarMonth, yearRange }: CustomMonthCaptionProps) {
   const { goToMonth, nextMonth, previousMonth } = useDayPicker();
+  const currentYear = new Date().getFullYear();
+  const startYear = yearRange?.start ?? currentYear - 50;
+  const endYear = yearRange?.end ?? currentYear + 49;
+  const years = Array.from({ length: endYear - startYear + 1 }, (_, i) => startYear + i);
   // ... use years array in the select
apps/webapp/app/components/runs/v3/SharedFilters.tsx (3)

115-128: Consider supporting additional time units.

The parsePeriodString regex only matches m, h, d units, but the parse-duration backend (per PR description) and the debounce system support additional units like w (weeks) and s (seconds). If users enter values with these units elsewhere in the system, the custom duration display may fall back to showing the raw period string.

♻️ Optional: Extend supported units
 const timeUnits = [
+  { label: "seconds", value: "s", singular: "second", shortLabel: "secs" },
   { label: "minutes", value: "m", singular: "minute", shortLabel: "mins" },
   { label: "hours", value: "h", singular: "hour", shortLabel: "hours" },
   { label: "days", value: "d", singular: "day", shortLabel: "days" },
+  { label: "weeks", value: "w", singular: "week", shortLabel: "weeks" },
 ];

 function parsePeriodString(period: string): { value: number; unit: string } | null {
-  const match = period.match(/^(\d+)([mhd])$/);
+  const match = period.match(/^(\d+)([smhdw])$/);
   if (match) {
     return { value: parseInt(match[1], 10), unit: match[2] };
   }
   return null;
 }

659-659: Prefer addDays over subDays with negative value for clarity.

Using subDays(saturday, -1) works but is counter-intuitive. Consider using addDays from date-fns for better readability.

♻️ Proposed improvement
+import { addDays } from "date-fns";
 // ...
-const sunday = endOfDay(subDays(saturday, -1));
+const sunday = endOfDay(addDays(saturday, 1));

458-476: Consider removing unconditional autoFocus on custom duration input.

The autoFocus attribute will focus this input every time the dropdown opens, even if the user intends to select a preset duration. This may interfere with keyboard navigation and preset button clicks. Consider conditionally applying focus only when the custom duration is already selected.

♻️ Proposed fix
   <input
     type="number"
     min="1"
     step="1"
     placeholder="Custom"
     value={customValue}
-    autoFocus
+    autoFocus={selectedPeriod === "custom"}
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 839d5e8 and 0cbed51.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/package.json
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/package.json
  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/primitives/DateTimePicker.tsx
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
  • apps/webapp/app/components/primitives/Calendar.tsx
  • apps/webapp/app/components/primitives/DateField.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-27T16:27:48.109Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:27:48.109Z
Learning: pnpm version `10.23.0` and Node.js version `20.11.1` are required for development

Applied to files:

  • apps/webapp/package.json
📚 Learning: 2025-12-18T14:09:10.154Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2794
File: internal-packages/run-engine/src/engine/systems/debounceSystem.ts:390-397
Timestamp: 2025-12-18T14:09:10.154Z
Learning: In the debounce system (internal-packages/run-engine/src/engine/systems/debounceSystem.ts), millisecond delays are not supported. The minimum debounce delay is 1 second (1s). The parseNaturalLanguageDuration function supports w/d/hr/h/m/s units only.

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧬 Code graph analysis (1)
apps/webapp/app/components/primitives/Calendar.tsx (1)
apps/webapp/app/utils/cn.ts (1)
  • cn (77-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
apps/webapp/app/components/primitives/DateField.tsx (1)

3-8: LGTM!

Clean import consolidation. Using type keyword for type-only imports (DateFieldState, DateSegment, Granularity) follows TypeScript best practices and enables proper tree-shaking.

apps/webapp/app/components/primitives/Calendar.tsx (1)

76-126: LGTM!

Well-structured Calendar component with comprehensive styling, proper accessibility (sr-only caption, aria-labels), and clean prop spreading. The weekStartsOn={1} for Monday start is a good international default.

apps/webapp/app/components/primitives/DateTimePicker.tsx (2)

57-67: Consider the UX when time is set before date.

When value is undefined and the user enters a time, the code creates new Date() (today). This means time can be set before date selection, defaulting to "today". This might be intentional UX, but consider whether you want to require a date selection first or make this behavior explicit in the UI.


77-144: LGTM!

Clean component structure with good accessibility (aria-label on time input), proper popover handling, and well-organized optional action buttons. The hidden calendar picker indicator for the time input is a nice touch for visual consistency.

apps/webapp/app/components/runs/v3/SharedFilters.tsx (3)

3-17: LGTM!

Good use of tree-shakeable date-fns imports for the specific date manipulation functions needed by the quick date buttons.


787-807: LGTM!

Clean helper component that follows the existing Button patterns and properly handles the active state styling.


423-536: Well-designed duration section UI.

The two-mode design (duration vs date range) with visual radio button indication provides clear UX. The preset buttons syncing with the custom input values is a nice touch for user convenience.

apps/webapp/package.json (1)

140-140: Dependency versions are current and stable.

Both date-fns (^4.1.0) and react-day-picker (^9.13.0) are confirmed as the latest stable versions and are appropriately specified. Dependencies are properly ordered alphabetically and the JSON formatting is correct.

Comment on lines +42 to +55
const handleDateSelect = (date: Date | undefined) => {
if (date) {
// Preserve the time from the current value if it exists
if (value) {
date.setHours(value.getHours());
date.setMinutes(value.getMinutes());
date.setSeconds(value.getSeconds());
}
onChange?.(date);
} else {
onChange?.(undefined);
}
setOpen(false);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid mutating the incoming date parameter.

The handleDateSelect function mutates the date object directly via setHours, setMinutes, and setSeconds. This can cause unexpected side effects if the caller retains a reference to the original date object.

🔧 Proposed fix: Create a new Date object
   const handleDateSelect = (date: Date | undefined) => {
     if (date) {
       // Preserve the time from the current value if it exists
       if (value) {
-        date.setHours(value.getHours());
-        date.setMinutes(value.getMinutes());
-        date.setSeconds(value.getSeconds());
+        const newDate = new Date(date);
+        newDate.setHours(value.getHours());
+        newDate.setMinutes(value.getMinutes());
+        newDate.setSeconds(value.getSeconds());
+        onChange?.(newDate);
+        setOpen(false);
+        return;
       }
       onChange?.(date);
     } else {
       onChange?.(undefined);
     }
     setOpen(false);
   };

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

@ericallam ericallam changed the title chore(webapp): Upgrade to the dateTime filter UI feat(webapp): Upgrade to the dateTime filter UI Jan 9, 2026
@ericallam ericallam changed the title feat(webapp): Upgrade to the dateTime filter UI feat(dashboard): Upgrade to the dateTime filter UI Jan 9, 2026
@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: chore(webapp): Upgrade to the dateTime filter UI

This is a well-structured UI/UX improvement that adds flexibility to the date/time filtering system. The implementation adds custom duration input and a new DateTimePicker component. Here is my detailed review:


Code Quality and Best Practices

Positives:

  • Clean separation of concerns with new reusable Calendar.tsx and DateTimePicker.tsx components
  • Good use of TypeScript with proper type definitions
  • Consistent styling patterns using the existing cn() utility and design system classes
  • Proper use of type imports in DateField.tsx (line 3-8)
  • Good accessibility attributes (aria-label on navigation buttons, time input)

Suggestions:

  1. Calendar.tsx:53-60 - The year range calculation new Date().getFullYear() - 50 + i generates years from 50 years ago to 50 years in the future. Consider making this configurable via props or using a smaller/more relevant range for a "runs filter" context.

  2. SharedFilters.tsx:326 - The SectionType type is defined inline. Consider extracting it to a named type at the top of the file for consistency with other types like TimeRangeType.

  3. SharedFilters.tsx:464 - autoFocus on the input field could be disruptive for accessibility. Consider making this configurable or using a more controlled focus management approach.


Potential Bugs or Issues

  1. DateTimePicker.tsx:42-55 - In handleDateSelect, the date object is mutated directly:

    date.setHours(value.getHours());
    date.setMinutes(value.getMinutes());
    date.setSeconds(value.getSeconds());

    While this works because date is a new Date from react-day-picker, it would be safer to create a new Date instance to avoid potential mutation issues:

    const newDate = new Date(date);
    if (value) {
      newDate.setHours(value.getHours());
      // ...
    }
    onChange?.(newDate);
  2. SharedFilters.tsx:353 - The useEffect dependency array is missing defaultPeriod:

    useEffect(() => {
      // ...
    }, [period, from, to]); // Missing defaultPeriod
  3. SharedFilters.tsx:659 - The calculation subDays(saturday, -1) for Sunday is correct but could be clearer. Consider using addDays(saturday, 1) for readability.

  4. SharedFilters.tsx:673 - Similarly, subDays(monday, -4) for Friday calculation could use addDays(monday, 4) for clarity.


Performance Considerations

  1. Calendar.tsx:38-41, 53-60 - The Array.from() calls for months and years are recreated on every render. Consider memoizing these arrays or moving them outside the component.

  2. SharedFilters.tsx:355-358 - The isCustomDurationValid calculation is wrapped in an IIFE that runs on every render. Consider using useMemo:

    const isCustomDurationValid = useMemo(() => {
      const value = parseInt(customValue, 10);
      return !isNaN(value) && value > 0;
    }, [customValue]);
  3. The applySelection callback has many dependencies. This is fine but worth monitoring for potential re-render issues.


Security Concerns

No significant security concerns identified. The code:

  • Uses proper input validation for the custom duration (checks for valid positive numbers)
  • Does not involve direct user input being used in dangerous contexts
  • URL parameters are properly handled through the existing useSearchParams hook

Test Coverage

No test files were added for the new components. Given the complexity of the date/time logic, consider adding tests for:

  1. parsePeriodString function - Unit tests for various inputs including edge cases:

    • Valid inputs: "90m", "2h", "7d"
    • Invalid inputs: "abc", "90", "90x", ""
  2. getInitialCustomDuration function - Test the parsing logic

  3. timeFilters function - Test the different combinations of period/from/to

  4. DateTimePicker component - Consider adding component tests for:

    • Date selection preserves time
    • Time change updates correctly
    • Clear/Now button functionality
  5. QuickDateButton presets - Test that "Yesterday", "This week", "Last weekend" etc. calculate correct date ranges


Additional Observations

  1. Good UX decisions:

    • Clear visual distinction between duration and date range modes using radio buttons
    • Quick select presets are a nice addition
    • Keyboard shortcut (Cmd/Ctrl+Enter) for applying selection
  2. Minor nit: The commit history shows 30 commits for this feature. Consider squashing into fewer, more meaningful commits before merging to main.

  3. Package.json changes: Two new dependencies were added. Ensure these are properly vetted and the bundle size impact is acceptable.


Summary

Overall this is a solid PR that improves the user experience for date/time filtering. The main recommendations are:

  1. High priority: Add unit tests for the date parsing/calculation functions
  2. Medium priority: Fix the potential mutation issue in DateTimePicker
  3. Low priority: Consider the performance optimizations mentioned above

Nice work on this feature! 🎯

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: feat(dashboard): Upgrade to the dateTime filter UI

Thanks for this well-structured UI/UX improvement to the date/time picker! The new dual-mode approach (duration vs date range) with custom duration support is a nice enhancement. Here's my detailed review:


✅ Code Quality & Best Practices

Strengths:

  • Clean component separation with Calendar.tsx and DateTimePicker.tsx as reusable primitives
  • Consistent use of the project's cn() utility for className merging
  • Follows the existing pattern of using "use client" directive for client components
  • Good TypeScript typing throughout
  • The DateField.tsx cleanup removes unused imports (BellAlertIcon, XMarkIcon) and improves import organization using inline type imports per the ESLint config

Suggestions:

  1. Unused import in SharedFilters.tsx (line 30): The Paragraph component is imported but only used once in what appears to be an error message container. Consider if this is intentional or if it should be a <p> tag instead:

    import { Paragraph } from "~/components/primitives/Paragraph";
  2. Consider extracting quick date range options: The inline array of quick date options (Today, Yesterday, This week, etc.) in TimeDropdown is quite large (~100 lines). Consider extracting this to a constant outside the component for better readability:

    const QUICK_DATE_OPTIONS = [
      { label: "Today", value: "today", getRange: () => ... },
      // ...
    ];
  3. Calendar year range: The calendar shows years from currentYear - 50 to currentYear + 49. For a "runs created" filter, showing 50 years in the future seems excessive. Consider limiting to current year or current year + 1.


🐛 Potential Bugs & Issues

  1. Date mutation in handleDateSelect (DateTimePicker.tsx:42-47): The code mutates the date parameter directly:

    if (value) {
      date.setHours(value.getHours());
      date.setMinutes(value.getMinutes());
      date.setSeconds(value.getSeconds());
    }

    This mutates the Date object passed from the calendar. While it works, creating a new Date would be safer:

    const newDate = new Date(date);
    newDate.setHours(value.getHours());
    // ...
    onChange?.(newDate);
  2. Weekend calculation edge case (SharedFilters.tsx): The "This weekend" logic calculates days until Saturday with:

    const daysUntilSaturday = (6 - today.getDay() + 7) % 7 || 7;

    This returns 7 when today is Saturday (since (6 - 6 + 7) % 7 = 0, then || 7 makes it 7). But there's already a check for isSaturday(today) above, so this code path shouldn't execute on Saturday. The logic is correct but could be simplified since the Saturday/Sunday cases are already handled.

  3. Time input behavior when no date selected: In DateTimePicker.tsx, if a user changes the time input before selecting a date, the code creates a new Date with new Date() (current date). This might be unexpected UX - the user might expect they need to select a date first.


⚡ Performance Considerations

  1. Re-renders in TimeDropdown: The component has many useState hooks and the inline quick date options array is recreated on every render. Consider:

    • Memoizing the quick date options array
    • Using useCallback for handlers that are passed as props
  2. Effect dependency (SharedFilters.tsx:350): The useEffect syncing state when props change includes defaultPeriod in the dependency array. Since defaultPeriod has a default value, this should be stable, but verify this doesn't cause unnecessary re-syncs.


🔒 Security Concerns

No security concerns identified. The component:

  • Uses controlled inputs properly
  • Parses user input safely with parseInt and regex matching
  • Doesn't involve any API calls or data persistence directly

🧪 Test Coverage

Current state: No tests found for SharedFilters.tsx, Calendar.tsx, or DateTimePicker.tsx components.

Recommendations: Consider adding tests for:

  • parsePeriodString() function - various inputs like "90m", "2h", "7d", invalid inputs
  • getInitialCustomDuration() function
  • Quick date range calculations, especially the weekend logic which has edge cases
  • Component rendering with different prop combinations

Even simple unit tests for the helper functions would add confidence for this logic-heavy component.


📝 Minor Nits

  1. The autoFocus attribute on the custom duration input (line 454) might be unexpected UX since the popover could open with focus on the duration input rather than allowing the user to choose duration vs date range first.

  2. Consider adding aria-label to the duration section buttons for better accessibility.


Summary

Overall this is a solid PR that improves the UX significantly. The main actionable items are:

  1. Fix the date mutation in DateTimePicker.tsx
  2. Consider extracting the quick date options constant
  3. Add basic unit tests for the parsing functions

Nice work on the clean UI separation! 🎉

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

🧹 Nitpick comments (3)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (3)

355-412: Consider using zod for validation per coding guidelines.

The manual validation logic works correctly, but the coding guidelines specify using zod for validation in apps/webapp. While this is simple UI state validation, consider defining a zod schema for consistency:

Example zod validation approach
import { z } from "zod";

const customDurationSchema = z.object({
  value: z.string().refine((val) => {
    const num = parseInt(val, 10);
    return !isNaN(num) && num > 0;
  }, "Please enter a valid custom duration"),
  unit: z.enum(["m", "h", "d"])
});

const dateRangeSchema = z.object({
  from: z.date().optional(),
  to: z.date().optional()
}).refine((data) => data.from || data.to, "Please specify at least one date");

As per coding guidelines, apps/webapp should use zod for validation.


458-477: Reconsider autoFocus on custom duration input.

The autoFocus attribute on line 464 will focus the custom input field whenever the dropdown opens, even if the user intends to select a preset duration. This could be distracting since most users may use presets rather than custom durations.

Consider removing autoFocus or only applying it when the dropdown opens with an existing custom value:

  <input
    type="number"
    min="1"
    step="1"
    placeholder="Custom"
    value={customValue}
-   autoFocus
+   autoFocus={selectedPeriod === "custom"}
    onChange={(e) => {

423-761: Consider splitting TimeDropdown into smaller components.

The TimeDropdown component is over 400 lines with complex state management and UI rendering. Consider extracting DurationSection and DateRangeSection as separate components to improve maintainability and testability.

Potential component structure
function DurationSection({ 
  active, 
  onActivate, 
  selectedPeriod, 
  onSelectPeriod, 
  customValue, 
  customUnit, 
  onCustomChange,
  validationError 
}) { /* ... */ }

function DateRangeSection({
  active,
  onActivate,
  fromValue,
  toValue,
  onFromChange,
  onToChange,
  selectedQuickDate,
  onQuickDateSelect,
  validationError
}) { /* ... */ }

// Then in TimeDropdown:
<DurationSection {...durationProps} />
<DateRangeSection {...dateRangeProps} />
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9abbb and 2cf6a98.

📒 Files selected for processing (1)
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-18T14:09:10.154Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2794
File: internal-packages/run-engine/src/engine/systems/debounceSystem.ts:390-397
Timestamp: 2025-12-18T14:09:10.154Z
Learning: In the debounce system (internal-packages/run-engine/src/engine/systems/debounceSystem.ts), millisecond delays are not supported. The minimum debounce delay is 1 second (1s). The parseNaturalLanguageDuration function supports w/d/hr/h/m/s units only.

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧬 Code graph analysis (1)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (9)
apps/webapp/app/hooks/useSearchParam.ts (1)
  • useSearchParams (7-64)
apps/webapp/app/components/primitives/RadioButton.tsx (1)
  • RadioButtonCircle (64-91)
apps/webapp/app/components/primitives/Label.tsx (1)
  • Label (25-41)
apps/webapp/app/utils/cn.ts (1)
  • cn (77-79)
scripts/analyze_marqs.mjs (1)
  • input (15-15)
apps/webapp/app/components/primitives/Buttons.tsx (1)
  • Button (296-329)
apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts (1)
  • span (1926-1943)
apps/webapp/app/components/primitives/DateTimePicker.tsx (1)
  • DateTimePicker (24-145)
apps/webapp/app/components/primitives/Paragraph.tsx (1)
  • Paragraph (88-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (9)

3-30: LGTM: Import additions support new date/time filtering features.

The new imports from date-fns and additional UI primitives are appropriate for the enhanced time filtering functionality.


130-134: LGTM: Default period validation.

The default period of 7 days is reasonable, and validating it at module load time ensures the constant is always valid.


138-197: LGTM: Default period parameter addition.

The defaultPeriod parameter with a fallback value of DEFAULT_PERIOD provides good flexibility while maintaining backward compatibility through the default value.


214-236: LGTM: Enhanced period rendering with custom duration support.

The logic correctly handles both preset periods and custom periods, with appropriate singular/plural formatting and sensible fallbacks.


296-305: LGTM: Clean helper function.

The getInitialCustomDuration function appropriately extracts custom duration state from a period string with sensible defaults.


325-353: LGTM: State management for two-mode filtering.

The state variables and synchronization effect appropriately support the duration vs. date-range modes. The useEffect dependency array correctly includes defaultPeriod.


787-807: LGTM: Clean reusable component.

The QuickDateButton component is well-designed for consistent rendering of quick-date options with appropriate visual feedback.


121-128: No changes needed. The regex pattern and time units are intentionally limited to minutes, hours, and days, which aligns with all preset period options defined in timePeriods. This is a consistent and complete implementation for the time filter feature. While week-based selections exist elsewhere in this file (for absolute date ranges like "This week"), they use absolute date functions rather than relative period strings, which is a different feature.


649-665: The last weekend date calculation logic is correct and properly handles all day-of-week cases:

  • Monday–Friday: previousSaturday() returns the most recent past Saturday, yielding the prior weekend
  • Saturday: subDays(now, 7) explicitly goes back to the previous Saturday, preventing inclusion of the current day
  • Sunday: subDays(now, 8) correctly goes back to the prior Saturday, preventing inclusion of yesterday's Saturday or today

The custom handling for Saturday and Sunday is intentional to ensure "last weekend" always refers to the most recent complete past weekend (Saturday–Sunday), consistent with other quick-date filters in the component (e.g., "Yesterday" excludes today). The logic is sound.

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: feat(dashboard): Upgrade to the dateTime filter UI

Thanks for this UI/UX improvement! The new date/time picker with custom duration support and quick date presets is a nice enhancement. Here's my feedback:

✅ What's Good

  1. Clean component structure: The new Calendar.tsx and DateTimePicker.tsx components are well-organized and follow good component composition patterns.

  2. Accessible UI: Good use of aria-label attributes on navigation buttons and time inputs.

  3. Consistent styling: The components follow the existing charcoal/indigo color scheme used throughout the app.

  4. Input validation: The TimeDropdown validates custom duration and date range inputs before applying.

  5. State synchronization: The useEffect in TimeDropdown properly syncs internal state when props change.

🔍 Suggestions for Improvement

1. Potential Date Mutation Bug (DateTimePicker.tsx:42-55)

The handleDateSelect function mutates the date parameter directly:

if (value) {
  date.setHours(value.getHours());
  date.setMinutes(value.getMinutes());
  date.setSeconds(value.getSeconds());
}
onChange?.(date);

This mutates the Date object from react-day-picker directly. Consider creating a copy first:

const newDate = new Date(date);
if (value) {
  newDate.setHours(value.getHours());
  // ...
}
onChange?.(newDate);

2. Time Input Edge Case (DateTimePicker.tsx:57-67)

When value is undefined and the user changes the time input, a new Date with the current date is created. This might be unexpected - the user might expect nothing to happen until they select a date first. Consider either:

  • Disabling the time input when no date is selected
  • OR documenting this as intentional behavior

3. Missing autoFocus Consideration (SharedFilters.tsx:469)

The custom duration input has autoFocus set unconditionally. This could cause unexpected scroll behavior or focus issues when the dropdown opens, especially if the user intends to use a preset button instead. Consider:

  • Only auto-focusing when selectedPeriod === "custom"
  • Or removing autoFocus entirely since users can click the input to focus it

4. Year Range in Calendar (Calendar.tsx:53-60)

The year dropdown generates 100 years centered on the current year (±50 years). For a date filter on "runs created," you likely only need dates from the past. Consider:

  • Limiting the range to past years only (e.g., current year down to 10 years ago)
  • Or making this configurable via props

5. Missing TypeScript strict null checks (SharedFilters.tsx:678)

The "Last weekdays" calculation:

const friday = endOfDay(subDays(monday, -4)); // Monday + 4 days = Friday

While the math is correct (subtracting -4 = adding 4), using addDays from date-fns would be clearer and more intentional.

📋 Test Coverage

No tests were added for the new components (Calendar.tsx, DateTimePicker.tsx) or the updated SharedFilters.tsx. Consider adding tests for:

  • Date selection preserves time correctly
  • Custom duration validation (invalid input, zero values, negative values)
  • Quick date presets calculate correct ranges
  • Edge cases like "Last weekend" calculation on Saturdays/Sundays

🔒 Security

No security concerns identified. The component handles user input appropriately without any injection risks.

⚡ Performance

The implementation looks performant. The use of startTransition for search value updates is a good practice. One minor note:

  • The year dropdown generates 100 <option> elements on every render. If performance becomes a concern, this could be memoized, but it's likely negligible.

Overall

This is a solid improvement to the date/time filter UX. The code is clean and well-organized. Addressing the date mutation bug and adding test coverage would make this even stronger.

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

🧹 Nitpick comments (4)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (4)

360-417: Consider using zod for validation.

The manual validation logic is correct, but the coding guidelines specify using zod for validation in apps/webapp. Consider refactoring to use zod schemas for custom duration and date range validation.

As per coding guidelines, validation in apps/webapp should use zod.

Example zod-based validation approach
import { z } from "zod";

const customDurationSchema = z.object({
  value: z.string().refine(
    (val) => {
      const num = parseInt(val, 10);
      return !isNaN(num) && num > 0;
    },
    { message: "Please enter a valid custom duration" }
  ),
  unit: z.enum(["m", "h", "d"]),
});

const dateRangeSchema = z
  .object({
    from: z.date().optional(),
    to: z.date().optional(),
  })
  .refine((data) => data.from || data.to, {
    message: "Please specify at least one date",
  })
  .refine(
    (data) => {
      if (data.from && data.to) {
        return data.from <= data.to;
      }
      return true;
    },
    { message: "From date must be before To date" }
  );

463-482: Consider removing autoFocus from custom duration input.

The autoFocus attribute on the custom duration input (line 469) will automatically focus the field every time the dropdown opens, which may be unexpected when users want to select a preset duration or date range instead.

Consider removing the autoFocus attribute to provide a more neutral initial state when the dropdown opens.


569-600: Remove negative margin layout hack.

Lines 569 and 585 use negative left margins (-ml-8) to align the DateTimePicker components. This is a fragile approach that couples the component to its parent's padding/layout.

Consider refactoring the layout structure to avoid negative margins, perhaps by adjusting the parent padding or using flexbox/grid alignment instead.


664-664: Use addDays instead of subDays with negative numbers.

Using subDays with negative numbers (lines 664 and 678) is semantically confusing. While mathematically correct (subtracting a negative adds), it reduces code clarity.

♻️ More readable alternative using addDays

For line 664:

-const sunday = endOfDay(subDays(saturday, -1));
+const sunday = endOfDay(addDays(saturday, 1));

For line 678:

-const friday = endOfDay(subDays(monday, -4)); // Monday + 4 days = Friday
+const friday = endOfDay(addDays(monday, 4)); // Monday + 4 days = Friday

Note: You'll need to import addDays from date-fns.

Also applies to: 678-678

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf6a98 and 68cd270.

📒 Files selected for processing (1)
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-18T14:09:10.154Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2794
File: internal-packages/run-engine/src/engine/systems/debounceSystem.ts:390-397
Timestamp: 2025-12-18T14:09:10.154Z
Learning: In the debounce system (internal-packages/run-engine/src/engine/systems/debounceSystem.ts), millisecond delays are not supported. The minimum debounce delay is 1 second (1s). The parseNaturalLanguageDuration function supports w/d/hr/h/m/s units only.

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧬 Code graph analysis (1)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (7)
apps/webapp/app/hooks/useSearchParam.ts (1)
  • useSearchParams (7-64)
apps/webapp/app/components/primitives/RadioButton.tsx (1)
  • RadioButtonCircle (64-91)
apps/webapp/app/components/primitives/Label.tsx (1)
  • Label (25-41)
apps/webapp/app/utils/cn.ts (1)
  • cn (77-79)
apps/webapp/app/components/primitives/Buttons.tsx (1)
  • Button (296-329)
apps/webapp/app/components/primitives/DateTimePicker.tsx (1)
  • DateTimePicker (24-145)
apps/webapp/app/components/primitives/Paragraph.tsx (1)
  • Paragraph (88-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (3)

792-812: LGTM - Clean component design.

The QuickDateButton component provides a consistent interface for quick date selections with clear active state indication. The implementation is straightforward and reusable.


732-761: Good keyboard shortcut implementation.

The Apply button includes a keyboard shortcut (Cmd/Ctrl+Enter) with enabledOnInputElements: true, which provides good UX by allowing users to apply filters without leaving input fields.


142-147: All signature changes are backward compatible; no breaking changes detected.

The functions timeFilters, timeFilterRenderValues, and TimeDropdown have been updated to accept an optional defaultPeriod parameter with default values. Verification across the codebase shows all 7 usages of timeFilters and all 6 usages of timeFilterRenderValues continue to use only the original parameters (period, from, to), and the TimeDropdown component has a single usage within the same file where it's defined. Since the new parameter is optional with a default value, all existing call sites remain fully compatible with these changes.

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: feat(dashboard): Upgrade to the dateTime filter UI

Summary

This PR significantly improves the date/time filtering experience in the dashboard by introducing a new dual-mode filter UI (duration vs date range), custom duration input, quick date selection options, and new reusable Calendar and DateTimePicker components.


Code Quality and Best Practices

Strengths:

  • Clean component architecture with well-organized separation of concerns
  • New Calendar.tsx and DateTimePicker.tsx components are properly exported as named exports (following project conventions)
  • Good use of TypeScript types (vs interfaces) per project guidelines
  • Proper use of cn() utility for className composition
  • Good accessibility support with aria-label attributes and keyboard navigation

Suggestions:

  1. Consider using Zod for validation (per project coding guidelines)

    The validation in applySelection() (lines 360-417 in SharedFilters.tsx) uses manual validation. Per the project's .github/copilot-instructions.md, Zod should be used for validation in apps/webapp:

    // Consider refactoring validation with Zod schemas
    const customDurationSchema = z.object({
      value: z.string().refine((val) => {
        const num = parseInt(val, 10);
        return \!isNaN(num) && num > 0;
      }, "Please enter a valid custom duration"),
      unit: z.enum(["m", "h", "d"])
    });
  2. Consider removing autoFocus on custom duration input (line 469)

    The autoFocus attribute will focus this input every time the dropdown opens, which may interfere with users who want to select preset durations. Consider:

    - autoFocus
    + autoFocus={selectedPeriod === "custom"}
  3. Use addDays instead of subDays with negative values (lines 652, 678)

    Using subDays(saturday, -1) is mathematically correct but semantically confusing:

    - const sunday = endOfDay(subDays(saturday, -1));
    + const sunday = endOfDay(addDays(saturday, 1));
  4. Component size consideration

    TimeDropdown is ~450 lines with complex state management. Consider extracting DurationSection and DateRangeSection into separate components for improved maintainability and testability.


Potential Bugs or Issues

  1. Cancel button doesn't fully reset state (lines 733-744)

    The Cancel button resets fromValue and toValue but doesn't reset duration-related state (selectedPeriod, customValue, customUnit, activeSection). This could lead to unexpected behavior if a user:

    • Opens dropdown (period: "7d")
    • Changes to custom duration "90m"
    • Clicks Cancel
    • Reopens dropdown - UI may show the unsaved custom state

    Consider also resetting:

    setSelectedPeriod(period ?? defaultPeriod);
    setCustomValue(getInitialCustomDuration(period).value);
    setCustomUnit(getInitialCustomDuration(period).unit);
    setActiveSection(from || to ? "dateRange" : "duration");
  2. Negative margin layout coupling (lines 569, 585)

    The -ml-8 classes create tight coupling between the DateTimePicker positioning and parent layout. Consider restructuring the layout to avoid negative margins.


Performance Considerations

  • Good: Tree-shakeable imports from date-fns (individual function imports)
  • Good: Lazy state initialization with useState initial values
  • Good: useCallback for applySelection to avoid unnecessary re-renders
  • Minor: The isCustomDurationValid IIFE runs on every render. Could be memoized with useMemo:
    const isCustomDurationValid = useMemo(() => {
      const value = parseInt(customValue, 10);
      return \!isNaN(value) && value > 0;
    }, [customValue]);

Security Concerns

No security concerns identified. The component handles user input safely with proper validation before applying to URL search params.


Test Coverage

Current state: No unit tests exist for the SharedFilters component or the new Calendar/DateTimePicker components.

Recommendations (per ai/references/tests.md guidelines):

  • Add tests for parsePeriodString() utility function
  • Add tests for timeFilters() and timeFilterRenderValues() functions
  • Add tests for date range validation logic
  • Add tests for quick date button calculations (especially edge cases like weekend calculations)

Example test structure:

describe("parsePeriodString", () => {
  it("parses valid period strings", () => {
    expect(parsePeriodString("90m")).toEqual({ value: 90, unit: "m" });
    expect(parsePeriodString("7d")).toEqual({ value: 7, unit: "d" });
  });

  it("returns null for invalid strings", () => {
    expect(parsePeriodString("invalid")).toBeNull();
    expect(parsePeriodString("7w")).toBeNull(); // weeks not supported
  });
});

Additional Notes

  • The PR adds react-day-picker (^9.13.0) and uses date-fns (^4.1.0) - both are current stable versions
  • Good UX decision to have the radio button visual indicator for section selection
  • The "Last weekend" calculation correctly handles all day-of-week edge cases
  • TypeScript conventions are followed (types over interfaces, no enums, no default exports)

Overall Assessment

This is a well-implemented feature that significantly improves the date/time filtering UX. The code is clean and follows most project conventions. The main areas for improvement are:

  1. Adding unit tests for the utility functions
  2. Using Zod for validation (per project guidelines)
  3. Minor UX polish (autoFocus behavior, cancel button state reset)

Verdict: Approve with minor suggestions above. None of the issues are blocking.

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

🤖 Fix all issues with AI agents
In @apps/webapp/app/components/runs/v3/SharedFilters.tsx:
- Around line 463-482: The input currently uses an unconditional autoFocus which
steals focus whenever the dropdown opens; remove the autoFocus prop from the
input and implement conditional focus via a ref and effect: create a ref (e.g.,
customInputRef), attach it to the input that currently uses
customValue/onChange/onFocus, and add a useEffect that calls
customInputRef.current?.focus() only when the dropdown is open and activeSection
=== "duration" and selectedPeriod === "custom" (depend on open, activeSection,
selectedPeriod); keep the existing onChange/onFocus handlers as-is but remove
the static autoFocus attribute so focus only occurs under the intended
conditions.
- Around line 733-745: The Cancel button handler currently only calls
setFromValue, setToValue, setValidationError, and setOpen, leaving other local
state (selectedPeriod, customValue, customUnit, activeSection,
selectedQuickDate) stale; update the Cancel onClick to also reset
selectedPeriod, customValue, customUnit, activeSection, and selectedQuickDate
back to their original/prop-derived values (or add a useEffect that watches the
dropdown open state and resets all those pieces of state when opened) so the
dropdown shows the original values after Cancel/close and reopen.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68cd270 and 1dd70d1.

📒 Files selected for processing (1)
  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-18T14:09:10.154Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2794
File: internal-packages/run-engine/src/engine/systems/debounceSystem.ts:390-397
Timestamp: 2025-12-18T14:09:10.154Z
Learning: In the debounce system (internal-packages/run-engine/src/engine/systems/debounceSystem.ts), millisecond delays are not supported. The minimum debounce delay is 1 second (1s). The parseNaturalLanguageDuration function supports w/d/hr/h/m/s units only.

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (4)

199-236: Enhanced period rendering with good UX.

The enhanced logic correctly handles both preset periods and custom periods with improved formatting (e.g., "90 mins" instead of "90m"). The fallback chain is well-structured.


792-812: LGTM: Clean implementation of QuickDateButton.

The component follows best practices with a function declaration, proper type annotations, and appropriate button attributes. The conditional styling for active state is well-implemented.


360-417: Validation logic is comprehensive and well-structured.

The applySelection callback properly validates both sections:

  • Duration: Ensures custom values are positive numbers
  • Date range: Ensures at least one date is specified and from ≤ to

The error messages are clear and user-friendly.


602-722: Quick date selections use date-fns correctly.

The date calculations for quick-select options are implemented correctly:

  • "Last weekend" properly handles different starting days
  • "Last weekdays" correctly calculates Monday through Friday
  • All date boundaries use appropriate start/end functions

Note: weekStartsOn: 1 (Monday) is used consistently, which is appropriate for international contexts.

Comment on lines +463 to +482
<input
type="number"
min="1"
step="1"
placeholder="Custom"
value={customValue}
autoFocus
onChange={(e) => {
setCustomValue(e.target.value);
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
onFocus={() => {
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
className="h-full w-full translate-y-px border-none bg-transparent py-0 pl-2 pr-0 text-xs leading-none text-text-bright outline-none placeholder:text-text-dimmed focus:outline-none focus:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider unconditional autoFocus on custom duration input.

The autoFocus attribute always focuses the custom duration input when the dropdown opens, regardless of the active section or selected period. This interrupts the expected interaction flow:

  1. When a preset duration is selected (e.g., "7d"), focus should not jump to the custom input
  2. When the date range section should be active, focus shouldn't be in the duration section
  3. Keyboard users are forced to tab out even if they want to select a preset or use quick dates
♻️ Suggested fix: Remove unconditional autoFocus
 <input
   type="number"
   min="1"
   step="1"
   placeholder="Custom"
   value={customValue}
-  autoFocus
   onChange={(e) => {

If focus management is desired, consider using a ref with conditional focus logic based on activeSection and selectedPeriod:

const customInputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
  if (open && activeSection === "duration" && selectedPeriod === "custom") {
    customInputRef.current?.focus();
  }
}, [open, activeSection, selectedPeriod]);

Then apply the ref to the input:

 <input
+  ref={customInputRef}
   type="number"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
type="number"
min="1"
step="1"
placeholder="Custom"
value={customValue}
autoFocus
onChange={(e) => {
setCustomValue(e.target.value);
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
onFocus={() => {
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
className="h-full w-full translate-y-px border-none bg-transparent py-0 pl-2 pr-0 text-xs leading-none text-text-bright outline-none placeholder:text-text-dimmed focus:outline-none focus:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
<input
type="number"
min="1"
step="1"
placeholder="Custom"
value={customValue}
onChange={(e) => {
setCustomValue(e.target.value);
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
onFocus={() => {
setSelectedPeriod("custom");
setActiveSection("duration");
setValidationError(null);
}}
className="h-full w-full translate-y-px border-none bg-transparent py-0 pl-2 pr-0 text-xs leading-none text-text-bright outline-none placeholder:text-text-dimmed focus:outline-none focus:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
/>
🤖 Prompt for AI Agents
In @apps/webapp/app/components/runs/v3/SharedFilters.tsx around lines 463 - 482,
The input currently uses an unconditional autoFocus which steals focus whenever
the dropdown opens; remove the autoFocus prop from the input and implement
conditional focus via a ref and effect: create a ref (e.g., customInputRef),
attach it to the input that currently uses customValue/onChange/onFocus, and add
a useEffect that calls customInputRef.current?.focus() only when the dropdown is
open and activeSection === "duration" and selectedPeriod === "custom" (depend on
open, activeSection, selectedPeriod); keep the existing onChange/onFocus
handlers as-is but remove the static autoFocus attribute so focus only occurs
under the intended conditions.

Comment on lines +733 to +745
<Button
variant="tertiary/small"
onClick={(e) => {
e.preventDefault();
setFromValue(from);
setToValue(to);
setValidationError(null);
setOpen(false);
}}
type="button"
>
Cancel
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Cancel button doesn't reset all state.

The Cancel button only resets fromValue, toValue, and validationError, but doesn't reset selectedPeriod, customValue, customUnit, activeSection, or selectedQuickDate. Since the useEffect (lines 345-353) only runs when props change, not when the dropdown opens, stale state persists across open/close cycles.

Scenario: User changes custom duration to "90m", clicks Cancel, then reopens the dropdown. The UI still shows "90m" instead of the original value because the state wasn't reset.

🐛 Proposed fix: Reset all state in Cancel handler
 <Button
   variant="tertiary/small"
   onClick={(e) => {
     e.preventDefault();
     setFromValue(from);
     setToValue(to);
+    const parsed = getInitialCustomDuration(period);
+    setCustomValue(parsed.value);
+    setCustomUnit(parsed.unit);
+    const isCustom = period && !timePeriods.some((p) => p.value === period) && parsed.value !== "";
+    setSelectedPeriod(isCustom ? "custom" : period ?? defaultPeriod);
+    setActiveSection(from || to ? "dateRange" : "duration");
+    setSelectedQuickDate(null);
     setValidationError(null);
     setOpen(false);
   }}
   type="button"
 >
   Cancel
 </Button>

Alternatively, add a useEffect that resets state when the dropdown opens:

+useEffect(() => {
+  if (open) {
+    const parsed = getInitialCustomDuration(period);
+    setCustomValue(parsed.value);
+    setCustomUnit(parsed.unit);
+    const isCustom = period && !timePeriods.some((p) => p.value === period) && parsed.value !== "";
+    setSelectedPeriod(isCustom ? "custom" : period ?? defaultPeriod);
+    setActiveSection(from || to ? "dateRange" : "duration");
+    setFromValue(from);
+    setToValue(to);
+    setValidationError(null);
+    setSelectedQuickDate(null);
+  }
+}, [open, period, from, to, defaultPeriod]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="tertiary/small"
onClick={(e) => {
e.preventDefault();
setFromValue(from);
setToValue(to);
setValidationError(null);
setOpen(false);
}}
type="button"
>
Cancel
</Button>
<Button
variant="tertiary/small"
onClick={(e) => {
e.preventDefault();
setFromValue(from);
setToValue(to);
const parsed = getInitialCustomDuration(period);
setCustomValue(parsed.value);
setCustomUnit(parsed.unit);
const isCustom = period && !timePeriods.some((p) => p.value === period) && parsed.value !== "";
setSelectedPeriod(isCustom ? "custom" : period ?? defaultPeriod);
setActiveSection(from || to ? "dateRange" : "duration");
setSelectedQuickDate(null);
setValidationError(null);
setOpen(false);
}}
type="button"
>
Cancel
</Button>
🤖 Prompt for AI Agents
In @apps/webapp/app/components/runs/v3/SharedFilters.tsx around lines 733 - 745,
The Cancel button handler currently only calls setFromValue, setToValue,
setValidationError, and setOpen, leaving other local state (selectedPeriod,
customValue, customUnit, activeSection, selectedQuickDate) stale; update the
Cancel onClick to also reset selectedPeriod, customValue, customUnit,
activeSection, and selectedQuickDate back to their original/prop-derived values
(or add a useEffect that watches the dropdown open state and resets all those
pieces of state when opened) so the dropdown shows the original values after
Cancel/close and reopen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants