Skip to content

Conversation

@samejr
Copy link
Member

@samejr samejr commented Jan 24, 2026

  • Better separation between main feature pages and manage pages
  • Toggle via keyboard shortcut Cmd/Ctrl + B or click collapse button
  • Collapsed state persisted to database (survives page refresh)
  • All menu items show tooltips when collapsed
  • Smooth animations using Framer Motion and CSS transitions
  • Impersonation-safe (preferences not modified when impersonating)
CleanShot.2026-01-25.at.19.53.13.mp4

Open with Devin

@changeset-bot
Copy link

changeset-bot bot commented Jan 24, 2026

⚠️ No Changeset found

Latest commit: f7fcedf

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

Walkthrough

This PR adds a collapsible side-menu feature and threads an isCollapsed prop across many UI components (SideMenu, SideMenuItem, SideMenuHeader, EnvironmentSelector, HelpAndFeedback, AskAI, IncidentStatusPanel, etc.), introducing collapsed/expanded layouts, tooltips, and motion animations. It persists side-menu preferences via a new route (resources.preferences.sidemenu.tsx) and service (dashboardPreferences.server.ts), updates types to include dashboard preferences, and adds keyboard shortcuts and several popover/tooltip adjustments.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is empty and does not follow the required template with sections for Testing, Changelog, and Screenshots. Add a detailed description following the template: include issue number, checklist items, testing steps, changelog summary, and any relevant screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.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 clearly describes the main feature: a collapsible side menu for the webapp, which aligns with the extensive changes throughout the codebase for this functionality.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
apps/webapp/app/components/navigation/SideMenu.tsx (1)

211-227: Wrap handleToggleCollapsed in useCallback for consistency.

Unlike handleManageSectionToggle, this handler isn't memoized. Since it's passed to useShortcutKeys and CollapseToggle, a new function reference on every render may cause the shortcut hook to re-register unnecessarily.

♻️ Suggested refactor
-  const handleToggleCollapsed = () => {
-    const newIsCollapsed = !isCollapsed;
-    setIsCollapsed(newIsCollapsed);
-    persistSideMenuPreferences({ isCollapsed: newIsCollapsed });
-  };
+  const handleToggleCollapsed = useCallback(() => {
+    setIsCollapsed((prev) => {
+      const newIsCollapsed = !prev;
+      persistSideMenuPreferences({ isCollapsed: newIsCollapsed });
+      return newIsCollapsed;
+    });
+  }, [persistSideMenuPreferences]);
📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d98bd7 and 6dc6baa.

📒 Files selected for processing (2)
  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.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

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.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/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.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/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.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

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
🧬 Code graph analysis (2)
apps/webapp/app/components/AskAI.tsx (4)
apps/webapp/app/assets/icons/AISparkleIcon.tsx (1)
  • AISparkleIcon (1-31)
apps/webapp/app/components/primitives/Buttons.tsx (1)
  • Button (296-329)
apps/webapp/app/components/primitives/Tooltip.tsx (4)
  • TooltipProvider (141-141)
  • Tooltip (141-141)
  • TooltipTrigger (141-141)
  • TooltipContent (141-141)
apps/webapp/app/components/primitives/ShortcutKey.tsx (1)
  • ShortcutKey (38-55)
apps/webapp/app/components/navigation/SideMenu.tsx (4)
apps/webapp/app/components/navigation/SideMenuHeader.tsx (1)
  • SideMenuHeader (7-69)
apps/webapp/app/components/navigation/EnvironmentSelector.tsx (1)
  • EnvironmentSelector (30-171)
apps/webapp/app/components/navigation/SideMenuItem.tsx (1)
  • SideMenuItem (9-93)
apps/webapp/app/components/navigation/SideMenuSection.tsx (1)
  • SideMenuSection (18-111)
⏰ 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). (20)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (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 (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (12)
apps/webapp/app/components/AskAI.tsx (4)

8-8: No review note for this import.


41-65: Collapsed-state prop threading looks consistent.
AskAI and the ClientOnly fallback both respect isCollapsed, which keeps UI behavior aligned.


69-75: AskAIProvider prop update looks good.
Defaulting isCollapsed here keeps the provider API clean and backward-compatible.


118-149: Collapsed tooltip positioning + layout animation look solid.
The side/offset adjustments and layout transition are coherent for the collapsed state.

apps/webapp/app/components/navigation/SideMenu.tsx (8)

107-109: LGTM! Type definition aligns with coding guidelines.

Using type over interface as per project conventions, and the narrowed pick from UserWithDashboardPreferences cleanly exposes only the necessary fields.


186-209: Cleanup logic properly flushes pending preferences — addressed from prior review.

The unmount cleanup correctly clears the timeout and submits any pending preference changes, preventing data loss when navigating away mid-debounce.


286-294: Good UX: hiding scrollbar when collapsed.

Removing the scrollbar in collapsed state (scrollbar-none) prevents visual clutter and improves the narrow sidebar appearance.


582-621: Tooltip fallback properly handles missing project name — addressed from prior review.

Line 614 now uses project.name ?? "Select a project" ensuring the tooltip never displays "undefined".


844-888: Clean implementation of collapsible helpers.

The CSS grid trick (grid-rows-[0fr]/grid-rows-[1fr]) for height animation is elegant and avoids JavaScript-based height measurement. Good separation between width-based (CollapsibleElement) and height-based (CollapsibleHeight) collapse behaviors.


902-982: LGTM! Clean SVG animation implementation.

The rotation logic with transform-origin and translateX compensation creates a smooth chevron animation that correctly indicates expand/collapse direction.


1000-1014: Accessibility improvements applied — addressed from prior review.

The button now includes type="button" and a dynamic aria-label that reflects the current state, ensuring proper accessibility for screen readers.


890-900: LGTM! Layout adapts well to collapsed state.

Using LayoutGroup enables smooth shared animations, and flex-col-reverse when collapsed sensibly reorders the elements for the narrow view.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@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: 4

Caution

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

⚠️ Outside diff range comments (2)
apps/webapp/app/routes/resources.incidents.tsx (2)

58-110: Prevent hidden incident controls from remaining tabbable.

Both panels are only hidden via height/opacity, so the LinkButton / PopoverTrigger can stay in the tab order when invisible. Consider adding aria-hidden and removing focusability when hidden.

♿ Suggested a11y hardening
-        <motion.div
+        <motion.div
           initial={false}
           animate={{
             height: isCollapsed ? 0 : "auto",
             opacity: isCollapsed ? 0 : 1,
           }}
           transition={{ duration: 0.15 }}
-          className="overflow-hidden"
+          aria-hidden={isCollapsed}
+          className={`overflow-hidden ${isCollapsed ? "pointer-events-none" : ""}`}
         >
@@
-            <LinkButton
+            <LinkButton
               variant="secondary/small"
               to="https://status.trigger.dev"
               target="_blank"
               fullWidth
               className="border-warning/20 bg-warning/10 hover:!border-warning/30 hover:!bg-warning/20"
+              tabIndex={isCollapsed ? -1 : undefined}
             >
@@
-        <motion.div
+        <motion.div
           initial={false}
           animate={{
             height: isCollapsed ? "auto" : 0,
             opacity: isCollapsed ? 1 : 0,
           }}
           transition={{ duration: 0.15 }}
-          className="overflow-hidden"
+          aria-hidden={!isCollapsed}
+          className={`overflow-hidden ${isCollapsed ? "" : "pointer-events-none"}`}
         >
@@
-              <PopoverTrigger className="flex !h-8 w-full items-center justify-center rounded border border-warning/20 bg-warning/10 transition-colors hover:border-warning/30 hover:bg-warning/20">
+              <PopoverTrigger
+                className="flex !h-8 w-full items-center justify-center rounded border border-warning/20 bg-warning/10 transition-colors hover:border-warning/30 hover:bg-warning/20"
+                tabIndex={!isCollapsed ? -1 : undefined}
+              >

34-46: Include fetchIncidents in the effect dependencies.

The effect uses fetchIncidents on lines 41 and 43 but omits it from the dependency array, which violates ESLint's exhaustive-deps rule. Although fetchIncidents is memoized and remains stable, the linter cannot verify this.

🔧 Suggested fix
-  }, []);
+  }, [fetchIncidents]);
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/AskAI.tsx`:
- Around line 118-135: The TooltipTrigger currently uses asChild to wrap a
non-focusable div, preventing keyboard focus from triggering the tooltip; move
the layout div (the container that uses isCollapsed to set "w-full" vs
"inline-flex") outside of TooltipProvider/TooltipTrigger and have TooltipTrigger
(with asChild) directly wrap the Button so the Button (which forwards refs)
becomes the trigger; ensure you keep the Button props (variant, data-action,
shortcut, hideShortcutKey, data-modal-override-open-class-ask-ai, onClick
calling openAskAI, and className conditional on isCollapsed) and preserve the
surrounding motion.div and TooltipProvider structure.

In `@apps/webapp/app/components/navigation/SideMenu.tsx`:
- Around line 984-996: The toggle button lacks a type and accessible name;
update the button element rendered alongside AnimatedChevron to include
type="button" and an appropriate accessible label (e.g., aria-label="Toggle
sidebar" or aria-label that reflects its action) and also add
aria-expanded={isCollapsed ? "false" : "true"} (or the inverse if your collapsed
logic differs) so screen readers get state; modify the button where
onClick={onToggle}, onMouseEnter/onMouseLeave and isHovering/isCollapsed are
used to include these attributes.
- Around line 566-605: The tooltip content currently interpolates project.name
directly and can show "undefined" when missing; update the SimpleTooltip's
content prop to use the same fallback as the button label (e.g., use
project.name ?? "Select a project") so the content reads `${organization.title}
/ ${project.name ?? "Select a project"}`; locate SimpleTooltip in SideMenu.tsx
and adjust its content prop accordingly to reference project.name with the
fallback.
- Around line 135-193: On unmount flush any pending debounced preference changes
so the last toggle isn’t lost: in the cleanup returned by the useEffect add
logic that (using pendingPreferencesRef and debounceTimeoutRef) first clears the
timeout (if any), then if pendingPreferencesRef.current has isCollapsed or
manageSectionCollapsed and user.isImpersonating is false, build a FormData the
same way persistSideMenuPreferences does and call preferencesFetcher.submit with
method "POST" and action "/resources/preferences/sidemenu", then clear
pendingPreferencesRef.current; keep the existing clearTimeout behavior and
ensure you reference persistSideMenuPreferences's pendingPreferencesRef,
debounceTimeoutRef and preferencesFetcher.submit to mirror the debounced submit.
🧹 Nitpick comments (2)
apps/webapp/app/components/Shortcuts.tsx (1)

84-87: Minor formatting inconsistency.

Line 85 is missing a space before the closing braces in the shortcut object, inconsistent with other similar usages in this file (e.g., line 75).

✨ Suggested fix
            <Shortcut name="Toggle side menu">
-              <ShortcutKey shortcut={{ modifiers: ["mod"]}} variant="medium/bright" />
+              <ShortcutKey shortcut={{ modifiers: ["mod"] }} variant="medium/bright" />
              <ShortcutKey shortcut={{ key: "b" }} variant="medium/bright" />
            </Shortcut>
apps/webapp/app/routes/resources.incidents.tsx (1)

67-92: Deduplicate the incident panel markup.

The expanded panel and IncidentPopoverContent render identical content, which is easy to drift. Reuse the helper component for both.

♻️ Suggested refactor
-          <div className="flex flex-col gap-2 rounded border border-warning/20 bg-warning/5 p-2 pt-1.5">
-            {/* Header */}
-            <div className="flex items-center gap-1 border-b border-warning/20 pb-1 text-warning">
-              <ExclamationTriangleIcon className="size-4" />
-              <Paragraph variant="small/bright" className="text-warning">
-                Active incident
-              </Paragraph>
-            </div>
-
-            {/* Description */}
-            <Paragraph variant="extra-small/bright" className="text-warning/80">
-              Our team is working on resolving the issue. Check our status page for more
-              information.
-            </Paragraph>
-
-            {/* Button */}
-            <LinkButton
-              variant="secondary/small"
-              to="https://status.trigger.dev"
-              target="_blank"
-              fullWidth
-              className="border-warning/20 bg-warning/10 hover:!border-warning/30 hover:!bg-warning/20"
-            >
-              <span className="text-warning">View status page</span>
-            </LinkButton>
-          </div>
+          <IncidentPopoverContent />

Also applies to: 126-147

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4093883 and 8d98bd7.

📒 Files selected for processing (17)
  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/services/dashboardPreferences.server.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{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

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/components/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.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/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/components/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.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/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.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

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/components/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/components/navigation/AccountSideMenu.tsx
  • apps/webapp/app/components/environments/EnvironmentLabel.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
  • apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
  • apps/webapp/app/components/navigation/SideMenuSection.tsx
  • apps/webapp/app/routes/resources.incidents.tsx
  • apps/webapp/app/components/Shortcuts.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/primitives/Popover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuItem.tsx
  • apps/webapp/app/components/AskAI.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/hooks/useUser.ts
  • apps/webapp/app/services/dashboardPreferences.server.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

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

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/dashboardPreferences.server.ts
🧠 Learnings (5)
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/resources.preferences.sidemenu.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to 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

Applied to files:

  • apps/webapp/app/hooks/useUser.ts
📚 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/routes/resources.incidents.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx
🧬 Code graph analysis (7)
apps/webapp/app/components/navigation/AccountSideMenu.tsx (2)
apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx (1)
  • HelpAndFeedback (32-254)
apps/webapp/app/components/AskAI.tsx (1)
  • AskAI (41-67)
apps/webapp/app/routes/resources.preferences.sidemenu.tsx (3)
apps/webapp/app/services/session.server.ts (1)
  • requireUser (39-62)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/services/dashboardPreferences.server.ts (1)
  • updateSideMenuPreferences (111-155)
apps/webapp/app/hooks/useUser.ts (1)
apps/webapp/app/models/user.server.ts (1)
  • UserWithDashboardPreferences (306-308)
apps/webapp/app/services/dashboardPreferences.server.ts (1)
apps/webapp/app/services/session.server.ts (1)
  • UserFromSession (37-37)
apps/webapp/app/components/Shortcuts.tsx (2)
apps/webapp/app/hooks/useShortcutKeys.tsx (1)
  • Shortcut (7-12)
apps/webapp/app/components/primitives/ShortcutKey.tsx (1)
  • ShortcutKey (38-55)
apps/webapp/app/components/primitives/Popover.tsx (2)
apps/webapp/app/hooks/useShortcutKeys.tsx (2)
  • useShortcutKeys (28-57)
  • ShortcutDefinition (14-19)
apps/webapp/app/components/primitives/ShortcutKey.tsx (1)
  • ShortcutKey (38-55)
apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx (2)
apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx (1)
  • HelpAndFeedback (32-254)
apps/webapp/app/components/AskAI.tsx (1)
  • AskAI (41-67)
⏰ 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). (20)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 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 (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
🔇 Additional comments (19)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam/route.tsx (1)

22-22: Layout update looks good.

The auto/1fr column split aligns with a collapsible side menu without affecting structure.

apps/webapp/app/components/environments/EnvironmentLabel.tsx (1)

83-90: Looks good: disableTooltip gating is clear and safe.

The prop addition and conditional render are straightforward and keep the default behavior intact.

Also applies to: 122-122

apps/webapp/app/components/Shortcuts.tsx (2)

2-4: LGTM!

Import reorganization looks good. All imports are used in the file.


28-29: LGTM!

Styling adjustments for button padding and icon spacing look fine.

apps/webapp/app/hooks/useUser.ts (1)

1-8: LGTM!

Clean type-level refactoring that introduces the User type alias for UserWithDashboardPreferences. This maintains backward compatibility for existing consumers while extending the user type to include dashboard preferences. The type-only imports are correctly applied.

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

152-191: LGTM!

The hideShortcutKey prop is well-implemented—it correctly hides the visual shortcut indicator while preserving the keyboard shortcut functionality (via useShortcutKeys). The conditional layout adjustments are appropriate for the collapsed state.

apps/webapp/app/components/navigation/SideMenuItem.tsx (1)

36-91: LGTM!

The tooltip-wrapped collapsible menu item implementation is clean. The framer-motion animations for width/opacity transitions provide smooth UX when toggling the collapsed state. The tooltip correctly appears only when collapsed (hidden={!isCollapsed}).

Minor note: The && !isCollapsed checks on lines 63 and 75 are defensive since the parent motion.div already animates to zero width/opacity, but this doesn't cause issues.

apps/webapp/app/services/dashboardPreferences.server.ts (2)

6-11: LGTM!

The Zod schema for SideMenuPreferences follows project conventions. Using z.infer for the type export is the correct pattern.


111-155: LGTM!

The updateSideMenuPreferences function follows established patterns in this file:

  • Respects impersonation state with early return
  • Provides sensible defaults for missing sideMenu
  • Supports partial updates (either isCollapsed or manageSectionCollapsed)
  • Avoids unnecessary database writes with change detection
apps/webapp/app/routes/resources.preferences.sidemenu.tsx (1)

1-39: LGTM!

The Remix action route correctly follows conventions:

  • Uses requireUser for authentication
  • Validates with Zod as per coding guidelines
  • Handles FormData string-to-boolean conversion appropriately

The manual boolean parsing before Zod validation is a practical approach for FormData, and the service layer handles the no-op case when nothing changes.

apps/webapp/app/components/navigation/EnvironmentSelector.tsx (2)

56-93: LGTM!

The refactored trigger with SimpleTooltip integration handles the collapsed state elegantly:

  • CSS transitions for width/opacity are lightweight and smooth
  • disableTooltip on EnvironmentLabel prevents tooltip conflicts
  • Tooltip shows full environment title only when collapsed
  • asChild and disableHoverableContent are correctly configured for popover interaction

94-99: LGTM!

Smart positioning adjustment: placing the popover to the right when collapsed (where there's more space) and below when expanded follows standard UX patterns for collapsible sidebars.

apps/webapp/app/components/navigation/SideMenuSection.tsx (1)

5-107: SideMenuSection collapse wiring looks solid.

The new isSideMenuCollapsed handling, divider animation, and item spacing hooks read cleanly.

apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx (1)

32-251: Nice collapsed trigger + shortcut integration.

The tooltip + popover trigger wiring and layout adjustments look consistent with the new collapsed UX.

apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx (1)

148-151: Bottom bar composition looks good.

Placing Help & Feedback and Ask AI side-by-side reads well for the new toolbar layout.

apps/webapp/app/components/navigation/AccountSideMenu.tsx (1)

59-62: Bottom bar alignment looks solid.

The Help & Feedback + Ask AI pairing fits the new toolbar layout nicely.

apps/webapp/app/components/navigation/SideMenuHeader.tsx (1)

7-52: Animated header collapse behavior looks solid.

The split-title and fade behavior reads clearly and aligns with the new collapsed UX.

apps/webapp/app/components/navigation/SideMenu.tsx (2)

227-529: Collapsed UI wiring across sections is cohesive.

Nice, consistent propagation of isCollapsed and layout transitions across header, items, and footer.


828-966: Helper components + animated chevron are clean and readable.

The collapsible helpers and chevron animation are well-scoped and easy to follow.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 984 to 996
<button
onClick={onToggle}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
className={cn(
"group flex h-12 w-6 items-center justify-center rounded-md text-text-dimmed transition-all duration-200 focus-custom",
isHovering
? "border border-grid-bright bg-background-bright shadow-md hover:bg-charcoal-750 hover:text-text-bright"
: "border border-transparent bg-transparent"
)}
>
<AnimatedChevron isHovering={isHovering} isCollapsed={isCollapsed} />
</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 | 🟠 Major

Add a button type and accessible label.

This button has no accessible name and can default to submit inside forms.

✅ Suggested fix
-            <button
+            <button
+              type="button"
+              aria-label={isCollapsed ? "Expand side menu" : "Collapse side menu"}
               onClick={onToggle}
               onMouseEnter={() => setIsHovering(true)}
               onMouseLeave={() => setIsHovering(false)}
🤖 Prompt for AI Agents
In `@apps/webapp/app/components/navigation/SideMenu.tsx` around lines 984 - 996,
The toggle button lacks a type and accessible name; update the button element
rendered alongside AnimatedChevron to include type="button" and an appropriate
accessible label (e.g., aria-label="Toggle sidebar" or aria-label that reflects
its action) and also add aria-expanded={isCollapsed ? "false" : "true"} (or the
inverse if your collapsed logic differs) so screen readers get state; modify the
button where onClick={onToggle}, onMouseEnter/onMouseLeave and
isHovering/isCollapsed are used to include these attributes.

@samejr samejr marked this pull request as ready for review January 25, 2026 19:57
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View issue and 6 additional flags in Devin Review.

Open in Devin Review

Comment on lines 28 to 29
if (!isManagedCloud) {
return null;

Choose a reason for hiding this comment

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

🔴 React hooks called after early return violates Rules of Hooks

The IncidentStatusPanel component calls React hooks (useFetcher, useCallback, useEffect) after an early return statement, which violates React's Rules of Hooks.

Click to expand

Issue Details

At resources.incidents.tsx:26-46, the component has this structure:

export function IncidentStatusPanel({ isCollapsed = false }) {
  const { isManagedCloud } = useFeatures();
  if (!isManagedCloud) {
    return null;  // Early return BEFORE other hooks
  }

  const fetcher = useFetcher<typeof loader>();  // Hook after conditional return!
  const fetchIncidents = useCallback(...);       // Hook after conditional return!
  useEffect(...);                                // Hook after conditional return!

React's Rules of Hooks require that hooks must be called in the same order on every render. When isManagedCloud is false, the component returns early and the subsequent hooks are not called. When isManagedCloud becomes true, the hooks are suddenly called, breaking React's expectation of consistent hook call order.

Impact

This can cause:

  • React errors about hooks being called in different order between renders
  • Unexpected component behavior when isManagedCloud value changes
  • Potential crashes in development mode with React's strict hook checking

(Refers to lines 28-46)

Recommendation: Move all hooks to the top of the component before any conditional returns. Either move the early return after all hooks, or refactor the component to have the conditional rendering inside the JSX return statement.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants