Skip to content

Conversation

@ericallam
Copy link
Member

Replaces redirectDocument with useFetcher for editing environment variables. This allows background form submission without full page reload, which preserves:

  • Scroll position in the env vars list
  • "Reveal values" toggle state
  • Search filter state

Fixes #2845

Generated with Claude Code

…le state

Replaces `redirectDocument` with `useFetcher` for editing environment
variables. This allows background form submission without full page
reload, which preserves:
- Scroll position in the env vars list
- "Reveal values" toggle state
- Search filter state

Changes:
- EditEnvironmentVariablePanel now uses `useFetcher` instead of `Form`
- Action returns `json({ success: true })` instead of `redirectDocument`
- Dialog auto-closes on successful submission via useEffect
- Form ID is now unique per variable to prevent conflicts

Fixes #2845

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

changeset-bot bot commented Jan 8, 2026

⚠️ No Changeset found

Latest commit: 32e371c

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

This change refactors the environment-variables route component to use the useFetcher hook for handling edit operations. The edit form now submits via fetcher.Form with a unique identifier, and a useEffect hook automatically closes the edit dialog upon successful submission. The corresponding action function returns a JSON response with a success flag instead of performing a full-page redirect. The changes consolidate the edit flow into a non-blocking fetcher pattern while maintaining existing functionality.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: using useFetcher for environment variable edits to preserve UI state (scroll/toggle), which is the primary focus of this PR.
Description check ✅ Passed The description provides a clear explanation of the change and its benefits, though it lacks some checklist items from the template like testing steps and screenshots.
Linked Issues check ✅ Passed The PR successfully addresses issue #2845 by replacing redirectDocument with useFetcher for env var edits, preserving scroll position, toggle state, and search filters without full-page reloads.
Out of Scope Changes check ✅ Passed All changes are directly related to the useFetcher implementation for environment variable edits and are within the scope of fixing issue #2845.

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

PR Review: fix(webapp): use useFetcher for env var edits to preserve scroll/toggle state

Summary

This PR addresses issue #2845 by replacing redirectDocument with useFetcher for editing environment variables. This is a good UX improvement that prevents full page reloads when editing env vars.


✅ What's Good

  1. Correct pattern usage: Using useFetcher is the right approach for background form submissions in Remix. This preserves scroll position, reveal toggle state, and search filter state as intended.

  2. Unique form IDs: The change to use edit-environment-variable-${variable.id}-${variable.environment.id} ensures each form instance has a unique ID, preventing conflicts when multiple edit dialogs exist.

  3. Clean dialog close logic: The useEffect hook that closes the dialog on successful submission is a reasonable approach.


⚠️ Potential Issues

1. Data revalidation after successful edit

When using useFetcher, the page data is automatically revalidated on successful mutations. However, since the action now returns json({ ...submission, success: true }) instead of a redirect, you should verify that Remix is properly revalidating the loader data to show the updated environment variable value in the list.

Suggestion: Test this scenario:

  1. Edit an env var value
  2. Confirm the list updates to show the new value without manual refresh

2. Type safety with lastSubmission

const lastSubmission = fetcher.data as any;

The existing TODO: type this comment remains, but now the typing is even more loose. The fetcher.data could be properly typed using the typeof action generic that's already being passed to useFetcher<typeof action>().

Minor improvement: Consider narrowing the type instead of casting to any:

const lastSubmission = fetcher.data;
// Then update useForm to handle the typed data

3. Inconsistent pattern between edit and delete

The delete action still uses redirectWithSuccessMessage with a full page redirect, while edit now uses fetcher. This means:

  • Editing preserves scroll/toggle state ✅
  • Deleting resets scroll/toggle state ❌

If users frequently delete multiple env vars in succession (as mentioned in the issue about updating "10 env vars"), they'll still experience the same pain point for deletes.

Suggestion: Consider applying the same pattern to the delete action for consistency. This could be a follow-up PR.

4. Success state persistence

The lastSubmission?.success flag persists in fetcher.data even after the dialog closes. If a user:

  1. Edits a var successfully (dialog closes)
  2. Opens the same edit dialog again
  3. The useEffect might trigger immediately because lastSubmission?.success is still true

Suggestion: Reset the success state when the dialog opens, or check that fetcher.state transitioned from non-idle to idle:

useEffect(() => {
  if (lastSubmission?.success && fetcher.state === "idle") {
    setIsOpen(false);
  }
}, [lastSubmission?.success, fetcher.state]);

Actually, looking more carefully - this could be fine because opening the dialog sets isOpen to true, and the effect only runs on dependency changes. But worth testing the edge case.

5. Loading state could be more specific

const isLoading = fetcher.state !== "idle";

This will show loading for both submitting and loading states. This is probably fine, but you could be more specific with fetcher.state === "submitting" if you only want to show loading during the actual submission phase.


🔒 Security

No security concerns identified. The PR maintains proper authentication checks and doesn't introduce any new attack vectors.


📊 Performance

Positive impact on performance:

  • Eliminates unnecessary full page reloads
  • Reduces server load by not re-fetching all page data unnecessarily
  • Better perceived performance for users

🧪 Test Coverage

No test files were modified or added. Given this is a UX-focused change:

  • Manual testing of the happy path is essential
  • Edge cases to test:
    • Edit multiple vars in succession
    • Edit a var, then try to edit the same var again
    • Edit a var while search filter is active
    • Edit a var with "Reveal values" toggled on
    • Submit with validation errors, then fix and resubmit

Verdict

Approve with minor suggestions

The PR achieves its goal of preserving UI state during env var edits. The implementation follows Remix patterns correctly. The suggestions above are minor improvements that could be addressed in follow-up PRs if needed.

🤖 Generated with Claude Code

@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 (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx (1)

407-410: Consider improving type safety.

Using as any at line 408 bypasses TypeScript's type checking. While this works at runtime, consider creating a proper type for the action response to improve type safety.

♻️ Suggested improvement

Define a type for the action response:

type EditActionResponse = {
  success?: boolean;
  value?: z.infer<typeof schema>;
  error?: Record<string, string[]>;
};

Then use it:

- const lastSubmission = fetcher.data as any;
+ const lastSubmission = fetcher.data as EditActionResponse | undefined;

Note: This also addresses the TODO comment at line 422.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c2e78c and 32e371c.

📒 Files selected for processing (1)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx
🧠 Learnings (3)
📚 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.env.$envParam.environment-variables/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/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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.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/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx
🧬 Code graph analysis (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx (2)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx (1)
  • action (118-179)
⏰ 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 / webapp / 🧪 Unit Tests: Webapp (4, 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 (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 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 (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (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 / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx (6)

12-12: LGTM! Imports added for fetcher pattern.

The useFetcher and useEffect imports are correctly added to support the non-blocking edit flow.

Also applies to: 18-18


161-161: LGTM! Correct fetcher response pattern.

Returning json({ ...submission, success: true }) instead of a redirect is the correct approach for the fetcher pattern. This allows the edit dialog to detect successful submission (line 414) and close automatically while preserving page state.


413-417: LGTM! Effect correctly handles dialog close timing.

The useEffect properly waits for both success flag and idle state before closing the dialog, preventing premature closure during submission.


420-420: LGTM! Unique form ID prevents conflicts.

Making the form ID unique per variable and environment is essential when multiple edit dialogs might be open simultaneously or when rapid successive edits occur.


438-438: LGTM! Fetcher form enables background submission.

Using fetcher.Form instead of Form is the key change that prevents full-page reloads and preserves scroll position, toggle state, and search filters as intended by the PR objectives.

Also applies to: 484-484


161-161: Verify silent success UX for edits.

The edit flow no longer shows a success toast message (unlike the delete flow at lines 172-180), providing a "silent success" experience where the dialog simply closes. This aligns with the PR's goal of smooth, non-disruptive updates, but please confirm this meets UX expectations.

Also applies to: 407-488

@ericallam ericallam merged commit 7d29f5a into main Jan 8, 2026
35 checks passed
@ericallam ericallam deleted the claude/issue-2845-20260108-1104 branch January 8, 2026 12:51
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.

feat: please stop fucking up scroll position after I update an env var

3 participants