Skip to content

Conversation

@ericallam
Copy link
Member

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.

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
@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2026

⚠️ No Changeset found

Latest commit: 97ecf38

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 8, 2026

Walkthrough

Adds parsing and state handling for custom time periods in SharedFilters: a timeUnits constant, parsePeriodString, and getInitialCustomDuration. Enhances period rendering to handle presets, parsed custom periods, and fallbacks. Extends TimeDropdown with customValue/customUnit state, validation (isCustomDurationValid), applyCustomDuration, and applyDateRange. Introduces UI for numeric custom-duration input with unit select and Apply (including Enter-key handling) and a From/To exact date-range section. Replaces direct apply usage with applyDateRange for date-range mode and adjusts layout spacing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. It lacks required sections: issue reference, testing details, changelog, and screenshots. Only a brief summary is provided. Add issue reference (Closes #), describe testing steps, provide a changelog entry, and include screenshots showing the custom time interval feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature addition: enabling custom time intervals instead of preset-only values in the run list dashboard.

✏️ 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.

@claude
Copy link

claude bot commented Jan 8, 2026

Code Review: Custom Time Intervals Feature

Thanks for this enhancement! Allowing users to specify custom time intervals is a great UX improvement over being limited to preset values. Here's my review:

Overall Assessment

The implementation is clean and well-structured. The code follows existing patterns in the codebase and integrates smoothly with the existing time filter system.


Code Quality & Best Practices

Strengths:

  • Good separation of concerns with helper functions (parsePeriodString, getInitialCustomDuration)
  • Consistent use of useCallback for memoized functions
  • Follows the existing code patterns in the file
  • Nice handling of singular/plural unit labels (e.g., "1 minute" vs "90 minutes")

Suggestions:

  1. Consider extracting isCustomDurationValid as a useMemo instead of an IIFE:

    // Current (lines 346-349):
    const isCustomDurationValid = (() => {
      const value = parseInt(customValue, 10);
      return !isNaN(value) && value > 0;
    })();
    
    // Suggested:
    const isCustomDurationValid = useMemo(() => {
      const value = parseInt(customValue, 10);
      return !isNaN(value) && value > 0;
    }, [customValue]);

    This provides proper memoization and follows React patterns used elsewhere in the codebase.

  2. Minor: The timeUnits array could use as const for better type inference:

    const timeUnits = [
      { label: "minutes", value: "m", singular: "minute" },
      { label: "hours", value: "h", singular: "hour" },
      { label: "days", value: "d", singular: "day" },
    ] as const;

Potential Bugs or Issues

  1. Custom duration state doesn't reset when dropdown reopens: If a user enters a custom value, closes the dropdown without applying, and reopens it, the previous unsaved value persists. Consider resetting customValue and customUnit when the dropdown opens, or initialize them based on the current period prop. The getInitialCustomDuration is only called once on mount, not on subsequent opens.

  2. Edge case with very large values: While the backend uses parse-duration, extremely large values (e.g., 999999 days) could cause issues. Consider adding a reasonable upper bound validation.

  3. Decimal values are accepted by number input: The input field accepts decimal values (e.g., "1.5"), but parseInt will truncate them silently. Consider using Math.floor or adding step="1" to the input to make behavior clearer.


Performance Considerations

The implementation is performant:

  • Callbacks are properly memoized with useCallback
  • No unnecessary re-renders identified
  • The regex parsing is simple and efficient

One minor note: getInitialCustomDuration(period) is called on every render since it's in the component body. This is a cheap operation, but if preferred, could be moved into a useMemo.


Security Concerns

No security issues identified. The custom period string is:

  • Constructed from validated numeric input + unit selection
  • Passed through the existing handlePeriodClick which uses the established URL parameter system
  • Eventually parsed by parse-duration on the backend

Test Coverage

No tests appear to be included in this PR. Consider adding:

  1. Unit tests for parsePeriodString:

    • Valid inputs: "90m", "2h", "7d"
    • Invalid inputs: "90", "m90", "90x", "", "abc"
  2. Unit tests for getInitialCustomDuration:

    • Various period strings and edge cases
    • Undefined/empty input
  3. Component tests for TimeDropdown:

    • Entering a custom value and applying
    • Validation behavior (disabled state)
    • Keyboard interaction (Enter key)

UI/UX Notes

  • The visual separation with the border between custom duration and exact time range is a nice touch
  • The "Or specify exact time range" label helps clarify the hierarchy of options
  • Consider adding the unit suffix in the display label for consistency (e.g., "mins" instead of "minutes" to match preset labels like "5 mins")

Summary

This is a well-implemented feature that enhances user experience. The main recommendations are:

  1. Consider resetting custom duration state when the dropdown reopens
  2. Add step="1" to the number input to prevent decimal confusion
  3. Add unit tests for the new helper functions
  4. Consider using useMemo for isCustomDurationValid

Nice work! 🎉

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

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

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 (2)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (2)

346-349: Consider simplifying the validation expression.

The IIFE pattern works but is a bit verbose. This could be a direct boolean expression for clarity.

♻️ Suggested simplification
-  const isCustomDurationValid = (() => {
-    const value = parseInt(customValue, 10);
-    return !isNaN(value) && value > 0;
-  })();
+  const parsedCustomValue = parseInt(customValue, 10);
+  const isCustomDurationValid = !isNaN(parsedCustomValue) && parsedCustomValue > 0;

389-402: Consider adding step="1" to indicate integer-only input.

The validation correctly rejects non-integer values, but type="number" allows decimal input by default. Adding step="1" provides a browser hint and improves UX for users who might try entering fractional values.

♻️ Suggested improvement
               <input
                 type="number"
                 min="1"
+                step="1"
                 placeholder="e.g. 90"
                 value={customValue}
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d29f5a and d54909e.

📒 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: Cursor Bugbot
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 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 (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (6)

98-111: LGTM!

The timeUnits array and parsePeriodString helper are well-structured. The regex correctly parses the supported duration formats, and returning null for invalid input provides safe fallback behavior.


193-215: LGTM!

The enhanced period rendering logic properly handles both preset and custom periods with appropriate fallback behavior. The singular/plural formatting for custom durations is a nice UX touch.


275-284: LGTM!

The initialization helper correctly parses the period and provides sensible defaults.


302-305: LGTM!

The custom duration state initialization is correctly scoped. The input fields maintain their own state for user editing, which is appropriate for this controlled form pattern.


428-429: LGTM!

The visual separator and descriptive label provide clear section organization.


478-486: LGTM!

The Apply button correctly calls applyDateRange() to persist the exact time range selection.

// Custom duration state
const initialCustom = getInitialCustomDuration(period);
const [customValue, setCustomValue] = useState(initialCustom.value);
const [customUnit, setCustomUnit] = useState(initialCustom.unit);
Copy link

Choose a reason for hiding this comment

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

Custom duration state not synced with period prop changes

Medium Severity

The customValue and customUnit state variables are initialized from getInitialCustomDuration(period), but useState only uses this value on first mount. When the period prop changes (e.g., after clicking a preset button), these state values won't update to reflect the new period. This causes the custom input fields to display stale values that don't match the currently active period, confusing users about which time range is actually selected.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

@claude could you fix this?

Copy link

@claude claude bot Jan 8, 2026

Choose a reason for hiding this comment

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

Claude finished @ericallam's task in 56s —— View job


Fixed custom duration state sync issue

  • Read the current implementation
  • Add useEffect to sync customValue/customUnit with period prop changes
  • Commit and push the fix

Added a useEffect hook that syncs customValue and customUnit state when the period prop changes. This ensures the custom input fields display the correct values after selecting a preset time period or when the period changes externally.

Changes: apps/webapp/app/components/runs/v3/SharedFilters.tsx

  • Added useEffect import
  • Added effect to update custom duration state when period prop changes
    |

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 <ericallam@users.noreply.github.com>
@claude
Copy link

claude bot commented Jan 8, 2026

PR Review: Custom Time Intervals for Run List Filters

Summary

This PR adds the ability for users to specify custom time intervals (e.g., 90 minutes) in the run list filters, instead of being limited to preset values. The implementation adds a numeric input with a unit dropdown that integrates with the existing parse-duration backend.


Code Quality and Best Practices

Strengths:

  • Clean separation of concerns with helper functions (parsePeriodString, getInitialCustomDuration)
  • Good use of React patterns (useCallback, useEffect for prop sync)
  • Consistent formatting and clear variable naming
  • Proper handling of Enter key for form submission

Suggestions:

  1. Memoize isCustomDurationValid: Currently it's recalculated on every render as an IIFE. Consider using useMemo:

    const isCustomDurationValid = useMemo(() => {
      const value = parseInt(customValue, 10);
      return !isNaN(value) && value > 0;
    }, [customValue]);
  2. Consider using the existing Input component: The codebase has a well-styled Input primitive at ~/components/primitives/Input.tsx with built-in variants and consistent styling. Using it would maintain UI consistency:

    <Input
      type="number"
      min="1"
      variant="small"
      placeholder="e.g. 90"
      ...
    />
  3. Consider a Select component for units: Similarly, there may be a styled select component that could replace the native <select> for visual consistency with other dropdowns in the app.


Potential Bugs or Issues

  1. No upper bound validation: Users could enter extremely large values (e.g., 999999999 days). Consider adding a reasonable maximum, especially since the backend may have its own constraints.

  2. State sync on period change could cause flicker: The useEffect that syncs customValue and customUnit when period changes will reset these fields whenever a preset is selected. This is probably fine, but worth noting that it clears any in-progress custom input when switching presets.

  3. Decimal input handling: The type="number" input allows decimal values (e.g., "90.5"), but parseInt will truncate them silently. Consider:

    • Adding step="1" to the input to discourage decimals
    • Or handling decimals explicitly with user feedback

Performance Considerations

The implementation is lightweight and shouldn't introduce any performance concerns:

  • Helper functions are simple O(1) operations
  • State updates are minimal and localized
  • No expensive computations or excessive re-renders

One minor optimization: getInitialCustomDuration is called twice during initialization (once for useState and once in the useEffect). This is negligible but could be consolidated using a ref for tracking prop changes instead.


Security Concerns

No security issues identified. The input is:

  • Validated before use (checked for NaN and positive values)
  • Parsed as an integer (no injection concerns)
  • Used only to construct simple period strings that integrate with existing validation

Test Coverage

There are no tests for this component. The SharedFilters.tsx file and its exported components (TimeFilter, TimeDropdown, timeFilters, etc.) currently lack test coverage.

Recommended test cases to add:

  1. parsePeriodString utility function:
    • Valid inputs: "90m", "2h", "7d"
    • Invalid inputs: "90", "m90", "90x", "", "abc"
  2. getInitialCustomDuration:
    • With valid period string
    • With undefined/empty input
  3. timeFilterRenderValues for custom periods:
    • Singular unit labels ("1 minute", "1 hour", "1 day")
    • Plural unit labels ("2 minutes", "90 hours")
  4. Component behavior:
    • Custom duration form submission
    • Enter key handling
    • Apply button disabled state

Additional Observations

  1. Good UX decision: Visual separation with the border between custom duration and exact time range sections improves clarity.

  2. Label consistency: The label "Or specify exact time range" nicely differentiates the two input methods.

  3. Accessibility: Consider adding aria-label attributes to the input and select elements for screen reader users.


Verdict

This is a well-implemented feature that cleanly extends existing functionality. The code follows the patterns established in the codebase.

Recommended before merge:

  • Consider using existing primitive components (Input) for consistency
  • Add step="1" to prevent decimal input confusion
  • Consider adding basic unit tests for the helper functions

🤖 Generated with Claude Code

@ericallam
Copy link
Member Author

@claude its working but it's pretty ugly. please make sure you are using existing components that make sense and also match o
CleanShot 2026-01-08 at 13 46 29
ur aesthetic

@claude
Copy link

claude bot commented Jan 8, 2026

Claude finished @ericallam's task in 1m 13s —— View job


Improving custom time interval UI styling

  • Read the current implementation and screenshot
  • Explore existing UI components in the codebase
  • Update the custom duration input to use existing styled components
  • Commit and push the changes

Updated the custom duration input to use the existing styled primitives:

  • Replaced raw <input> with Input component (variant small)
  • Replaced raw <select> with SimpleSelect components (SelectTrigger, SelectContent, SelectItem) with secondary/small size
  • Added step="1" to prevent decimal input confusion

These changes ensure the custom duration input matches the app's design system aesthetic.

Commit: 97ecf38

View job |

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)

344-356: Consider using zod for validation.

The inline validation works, but the coding guidelines specify using zod for validation in the webapp. Consider creating a zod schema for custom duration validation to align with project conventions.

As per coding guidelines: "Use zod for validation in packages/core and apps/webapp"

💡 Example zod schema
import { z } from "zod";

const customDurationSchema = z.object({
  value: z.number().int().positive().max(99999), // or appropriate max
  unit: z.enum(["m", "h", "d"])
});

344-356: Consider edge case handling for custom duration input.

The validation allows any positive number, but doesn't handle some edge cases:

  • No maximum value constraint (users could enter extremely large numbers)
  • The number input allows decimals, but parseInt silently truncates them
  • No user feedback for invalid input beyond disabling the Apply button

Consider adding max value validation and potentially showing error messages for better UX.


393-433: Consider improving accessibility for custom inputs.

The number input and select elements lack explicit aria-labels or associated label elements, which could impact screen reader users. Consider adding aria-label attributes for better accessibility.

♿ Accessibility improvement suggestion
 <input
   type="number"
   min="1"
   placeholder="e.g. 90"
+  aria-label="Custom duration value"
   value={customValue}
   ...
 />
 <select
   value={customUnit}
   onChange={(e) => setCustomUnit(e.target.value)}
+  aria-label="Custom duration unit"
   className="..."
 >
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d54909e and 74a4ca3.

📒 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 (6)
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Limit task duration using the `maxDuration` property (in seconds)

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 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 / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (3)

193-215: LGTM! Well-structured period rendering logic.

The enhanced logic appropriately handles presets, parses custom periods with proper singular/plural formatting, and includes sensible fallbacks.


308-312: Good fix for state synchronization.

This useEffect correctly addresses the stale value issue mentioned in the commit message, ensuring the custom input fields stay in sync when a preset is selected.


104-111: Add zod validation for the custom period input.

The inline validation (value > 0 check) should be replaced with a zod schema. Per the coding guidelines for apps/webapp/**/*.{ts,tsx}, use zod for validation instead of inline checks. Define a schema to validate the period string format before it's passed to the backend.

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 <ericallam@users.noreply.github.com>
@claude
Copy link

claude bot commented Jan 8, 2026

PR Review: Custom Time Intervals for Run List Filters

Thanks for this feature addition! Allowing custom time intervals is a nice UX improvement over being limited to preset values. Here's my feedback:

Overall Assessment

The implementation is clean and follows existing patterns well. The code integrates nicely with the existing parse-duration backend and reuses the project's component primitives.


Code Quality & Best Practices

Strengths:

  • Good separation of concerns with parsePeriodString and getInitialCustomDuration helper functions
  • Follows existing naming conventions and component patterns in the codebase
  • Proper use of useCallback for memoized handlers
  • The timeUnits array includes both plural and singular forms for proper grammar in labels

Minor Suggestions:

  1. The isCustomDurationValid calculation (lines 361-364) is computed on every render as an IIFE. Consider using useMemo:

    const isCustomDurationValid = useMemo(() => {
      const value = parseInt(customValue, 10);
      return !isNaN(value) && value > 0;
    }, [customValue]);
  2. The parsePeriodString function could potentially be typed more strictly. The unit return value is typed as string but really should be 'm' | 'h' | 'd':

    function parsePeriodString(period: string): { value: number; unit: 'm' | 'h' | 'd' } | null

Potential Bugs or Issues

  1. Integer overflow consideration: While unlikely in practice, entering extremely large numbers (e.g., 999999999999999) could potentially cause issues. Consider adding a reasonable max value check:

    const MAX_DURATION_VALUE = 9999; // or whatever makes sense
    const isCustomDurationValid = !isNaN(value) && value > 0 && value <= MAX_DURATION_VALUE;
  2. Decimal handling: The input allows decimal values to be typed (e.g., "1.5"), but parseInt will truncate them silently. This might be confusing for users. Consider:

    • Adding pattern="[0-9]*" to the input to prevent decimal entry on some browsers, OR
    • Showing validation feedback when decimals are entered
  3. Edge case with state sync: The useEffect that syncs customValue and customUnit when period changes will also reset these values when the dropdown reopens with the same period. This is fine but worth noting.


Performance Considerations

The implementation is performant. A few notes:

  • The component re-renders are minimal and expected
  • The useEffect has proper dependency array
  • No expensive computations or unnecessary re-renders introduced

The IIFE for isCustomDurationValid is technically re-evaluated on every render, but it's a trivial computation. Still, useMemo would be more idiomatic.


Security Concerns

No security concerns identified. The input is:

  • Only numeric values
  • Validated before being applied
  • The backend already uses parse-duration which handles the period string safely

Test Coverage

Missing: There appear to be no tests included with this PR. Consider adding:

  1. Unit tests for parsePeriodString:

    describe('parsePeriodString', () => {
      it('parses valid period strings', () => {
        expect(parsePeriodString('90m')).toEqual({ value: 90, unit: 'm' });
        expect(parsePeriodString('2h')).toEqual({ value: 2, unit: 'h' });
        expect(parsePeriodString('7d')).toEqual({ value: 7, unit: 'd' });
      });
      
      it('returns null for invalid strings', () => {
        expect(parsePeriodString('invalid')).toBeNull();
        expect(parsePeriodString('90x')).toBeNull();
        expect(parsePeriodString('')).toBeNull();
      });
    });
  2. Unit tests for getInitialCustomDuration

  3. Integration/component tests for the TimeDropdown behavior with custom duration


UI/UX Feedback

  1. The visual hierarchy is good - custom duration is between presets and the date range picker
  2. The "Or specify exact time range" label helps clarify the UI sections
  3. Keyboard support (Enter to apply) is a nice touch

One suggestion: Consider adding aria-label or aria-describedby to the custom duration input for better accessibility.


Summary

This is a solid feature addition. The main recommendations are:

  1. Consider adding tests for the new helper functions
  2. Consider using useMemo for isCustomDurationValid for consistency with React patterns
  3. Consider adding input validation for maximum value and decimal handling

Nice work! 👍

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 (2)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (2)

352-359: Consider adding maximum value validation.

While the function validates that the custom duration is positive, there's no upper bound check. Users could input extremely large values (e.g., 999999 days), which might cause performance issues or unexpected behavior in the backend.

Consider adding a reasonable maximum value based on your application's requirements.

🛡️ Proposed enhancement with max validation
 const applyCustomDuration = useCallback(() => {
   const value = parseInt(customValue, 10);
-  if (isNaN(value) || value <= 0) {
+  if (isNaN(value) || value <= 0 || value > 365) {
     return;
   }
   const periodString = `${value}${customUnit}`;
   handlePeriodClick(periodString);
 }, [customValue, customUnit, handlePeriodClick]);

Note: The maximum of 365 is an example; adjust based on your application's needs. You may also want to adjust this maximum based on the selected unit (e.g., 365 days, but 8760 hours).


361-364: Simplify the validation expression.

The IIFE pattern (() => { ... })() is unnecessarily complex for a simple validation check.

♻️ Proposed simplification
-const isCustomDurationValid = (() => {
-  const value = parseInt(customValue, 10);
-  return !isNaN(value) && value > 0;
-})();
+const value = parseInt(customValue, 10);
+const isCustomDurationValid = !isNaN(value) && value > 0;

Alternatively, if you want to memoize for performance:

-const isCustomDurationValid = (() => {
-  const value = parseInt(customValue, 10);
-  return !isNaN(value) && value > 0;
-})();
+const isCustomDurationValid = useMemo(() => {
+  const value = parseInt(customValue, 10);
+  return !isNaN(value) && value > 0;
+}, [customValue]);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74a4ca3 and 97ecf38.

📒 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 (7)
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Limit task duration using the `maxDuration` property (in seconds)

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/components/runs/v3/SharedFilters.tsx
📚 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 (3)
apps/webapp/app/components/primitives/Input.tsx (1)
  • Input (125-125)
apps/webapp/app/components/primitives/Select.tsx (3)
  • Select (112-258)
  • SelectTrigger (270-374)
  • SelectItem (452-501)
apps/webapp/app/components/primitives/SimpleSelect.tsx (2)
  • SelectValue (142-142)
  • SelectContent (136-136)
⏰ 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). (23)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 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 (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
apps/webapp/app/components/runs/v3/SharedFilters.tsx (11)

5-5: LGTM!

The new imports are appropriate for the custom duration feature and align with the app's design system primitives.

Also applies to: 9-9, 12-18


106-110: LGTM!

The timeUnits constant provides a clean structure for the custom duration UI with proper singular/plural handling.


112-119: LGTM!

The parsing logic correctly extracts value and unit from period strings, with proper null handling for invalid formats.


283-292: LGTM!

The function properly initializes custom duration state from period strings, with sensible defaults for empty/invalid periods.


201-223: LGTM!

The enhanced rendering logic elegantly handles both preset and custom periods, with proper singular/plural formatting and graceful fallbacks.


310-320: LGTM!

The custom duration state is properly initialized and synchronized with period prop changes, preventing stale state issues.


322-332: LGTM!

Extracting applyDateRange improves code reuse and maintainability.


401-445: LGTM!

The custom duration UI is well-structured with proper validation, keyboard navigation (Enter key), and appropriate use of the app's design system primitives. The disabled state on the Apply button ensures invalid input cannot be submitted.


447-448: LGTM!

The visual separator and label effectively distinguish the custom duration section from the exact date range section, improving usability.


500-500: LGTM!

Using the extracted applyDateRange callback improves code maintainability and eliminates duplication.


352-359: No compatibility issues found with parse-duration.

The frontend correctly generates period strings in the format ${value}${customUnit} (e.g., "90m", "2h", "7d") using the timeUnits array with values "m", "h", and "d". The backend uses the parse-duration library in multiple services (runsRepository, clickhouseRunsRepository, BulkActionV2, WaitpointListPresenter, BatchListPresenter, and api.v1.deployments) to parse these periods, with proper error handling in place (e.g., in BulkActionV2.server.ts which throws if parsing fails). The format is compatible with parse-duration's standard notation for durations.

Copy link
Collaborator

@0ski 0ski left a comment

Choose a reason for hiding this comment

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

🫨

@ericallam ericallam closed this Jan 9, 2026
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