Skip to content

feat: open single plan result from url#1034

Merged
adityachoudhari26 merged 1 commit intomainfrom
open-single-plan-from-url
Apr 22, 2026
Merged

feat: open single plan result from url#1034
adityachoudhari26 merged 1 commit intomainfrom
open-single-plan-from-url

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented Apr 22, 2026

fixes #1032

Summary by CodeRabbit

  • Refactor
    • Improved deployment plan diff viewing with centralized dialog management and URL state synchronization, enabling better bookmarking and sharing of specific plan comparisons.

Copilot AI review requested due to automatic review settings April 22, 2026 22:32
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

The changes refactor PlanDiffDialog into a fully controlled component that responds to open and onOpenChange props, introduce a new usePlanResultParam hook for synchronizing dialog state with URL query parameters, and update the deployment plan page to drive dialog visibility via URL-based resultId state.

Changes

Cohort / File(s) Summary
Dialog Refactoring
apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
Converted from internally-controlled (using DialogTrigger) to fully controlled component. Props signature changed to require open and onOpenChange; resultId now optional; children removed. TRPC query guarded to only execute when dialog is open and resultId is defined.
URL State Management
apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts
New custom hook that derives resultId from URL search parameters and provides openResult(id) and closeResult() actions to manipulate the query string while preserving other params.
Page Integration
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx
Integrated usePlanResultParam hook to manage dialog state via URL. Replaced inline diff dialogs with centralized PlanDiffDialog. Added resultTitle helper to compute dialog title from active result row.

Sequence Diagram

sequenceDiagram
    actor User
    participant Page as Page Component
    participant Hook as usePlanResultParam
    participant Dialog as PlanDiffDialog
    participant URL as URL/Query Params
    participant API as TRPC Query

    User->>Page: Click "View diff" button
    Page->>Hook: onViewDiff(resultId)
    Hook->>URL: Update search params with resultId
    URL->>Page: searchParams change triggers re-render
    Page->>Dialog: Pass open=true + resultId
    Dialog->>API: Execute resultDiff query (guarded by open && resultId)
    API->>Dialog: Return diff data
    Dialog->>User: Display diff content

    User->>Dialog: Click close/dismiss
    Dialog->>Page: Call onOpenChange(false)
    Page->>Hook: closeResult()
    Hook->>URL: Remove resultId from search params
    URL->>Page: searchParams change triggers re-render
    Page->>Dialog: Pass open=false
    Dialog->>User: Dialog closes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #1008 — Introduces the initial PlanDiffDialog component that this PR refactors into a fully controlled, URL-driven variant.

Suggested reviewers

  • jsbroks

Poem

🐰 A dialog once trapped in its state,
Now free as a rabbit, through URLs it'll skate!
With hooks that remember the path that you roam,
The diff finds its way, and you'll always find home. ✨

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: open single plan result from url' directly and clearly summarizes the main change: enabling URL-driven opening of individual plan results.
Linked Issues check ✅ Passed The PR implements URL-based plan result opening through a controlled dialog component, custom hook for URL/state sync, and refactored page logic, fulfilling issue #1032's objective.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of opening plan results via URL. The refactoring of PlanDiffDialog to controlled component, new usePlanResultParam hook, and page integration are all scoped to this feature.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch open-single-plan-from-url

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for deep-linking directly to a single deployment plan result diff by driving the diff dialog from a resultId URL query parameter (Issue #1032).

Changes:

  • Introduces a usePlanResultParam hook to read/write resultId in search params.
  • Refactors the plan results table to open a single, page-level PlanDiffDialog instead of per-row dialog triggers.
  • Updates PlanDiffDialog to be a controlled component (open / onOpenChange) and accept an optional resultId.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx Wires the results table “View diff” action to a URL param and renders a controlled diff dialog.
apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts New hook for managing resultId in the URL search params.
apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx Converts dialog to controlled mode and adjusts diff query behavior for optional resultId.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5 to +7
const resultId = searchParams.get("resultId") ?? undefined;

const openResult = (id: string) => {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

searchParams.get("resultId") can return an empty string when the URL contains ?resultId=. In that case resultId becomes "" (not undefined), which will cause the diff dialog to open and attempt to fetch a diff for an invalid id. Consider normalizing empty strings to undefined (e.g., treat "" as absent) and optionally guarding openResult against being called with an empty id.

Suggested change
const resultId = searchParams.get("resultId") ?? undefined;
const openResult = (id: string) => {
const resultId = searchParams.get("resultId") || undefined;
const openResult = (id: string) => {
if (!id) {
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +206 to +208
resultId={resultId}
title={activeResult ? resultTitle(activeResult) : ""}
open={resultId != null}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The dialog title can be an empty string when resultId is present in the URL but activeResult hasn’t loaded yet (or the id is invalid). This leaves the dialog without an accessible title and can create a confusing blank header. Consider providing a non-empty fallback title (e.g. "Plan diff"/"Loading…") and/or closing the dialog when resultId doesn’t match any loaded result.

Copilot uses AI. Check for mistakes.
Comment on lines 34 to +36
const diffQuery = trpc.deployment.plans.resultDiff.useQuery(
{ deploymentId, resultId },
{ enabled: open },
{ deploymentId, resultId: resultId ?? "" },
{ enabled: open && resultId != null },
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

enabled: open && resultId != null will still run the query when resultId is an empty string (e.g. URL ?resultId=). That will call the API with resultId: "" due to resultId ?? "". Tighten the guard to require a non-empty id (e.g. !!resultId) and avoid passing a placeholder empty string as the query input.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (1)
apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx (1)

146-212: ⚠️ Potential issue | 🟡 Minor

Dialog can open with an empty title (and fire a query) for a stale/invalid resultId in the URL.

When the page loads with ?resultId=... deep-linked, results is empty during the initial fetch, so activeResult is undefined and the dialog opens with an empty title. The same happens permanently if the URL resultId doesn't correspond to any row in results (stale link, wrong plan, etc.) — the dialog stays open with a blank header while PlanDiffDialog still issues resultDiff with that id.

Consider (a) gating open on activeResult != null once resultsQuery has settled, or (b) auto-calling closeResult() when results have loaded but no match is found, to avoid a stuck dialog with a blank title on invalid deep links.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx
around lines 146 - 212, The dialog can open with an empty title because
PlanDiffDialog's open prop only checks resultId while results are still loading
or the id is invalid; update the component to only open the dialog when an
actual result is found or to auto-close when results finish loading with no
match: change the PlanDiffDialog props to use open={resultId != null &&
activeResult != null && !resultsQuery.isLoading} (or alternatively, add an
effect that calls closeResult() when resultsQuery.isLoading becomes false and
activeResult is undefined) and keep title={activeResult ?
resultTitle(activeResult) : ""}; reference PlanDiffDialog, activeResult,
resultId, resultsQuery, closeResult, and resultTitle to locate the change.
🧹 Nitpick comments (1)
apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts (1)

3-20: Optional: stabilize callbacks and add explicit return type.

Minor polish — openResult/closeResult are recreated on every render, which will invalidate memoization for any consumer that depends on them. Also, per the repo's TS guideline to use explicit types, consider annotating the hook's return shape.

Proposed refactor
-import { useSearchParams } from "react-router";
-
-export function usePlanResultParam() {
-  const [searchParams, setSearchParams] = useSearchParams();
-  const resultId = searchParams.get("resultId") ?? undefined;
-
-  const openResult = (id: string) => {
-    const newParams = new URLSearchParams(searchParams);
-    newParams.set("resultId", id);
-    setSearchParams(newParams);
-  };
-
-  const closeResult = () => {
-    const newParams = new URLSearchParams(searchParams);
-    newParams.delete("resultId");
-    setSearchParams(newParams);
-  };
-
-  return { resultId, openResult, closeResult };
-}
+import { useCallback } from "react";
+import { useSearchParams } from "react-router";
+
+interface UsePlanResultParam {
+  resultId: string | undefined;
+  openResult: (id: string) => void;
+  closeResult: () => void;
+}
+
+export function usePlanResultParam(): UsePlanResultParam {
+  const [searchParams, setSearchParams] = useSearchParams();
+  const resultId = searchParams.get("resultId") ?? undefined;
+
+  const openResult = useCallback(
+    (id: string) => {
+      setSearchParams((prev) => {
+        const next = new URLSearchParams(prev);
+        next.set("resultId", id);
+        return next;
+      });
+    },
+    [setSearchParams],
+  );
+
+  const closeResult = useCallback(() => {
+    setSearchParams((prev) => {
+      const next = new URLSearchParams(prev);
+      next.delete("resultId");
+      return next;
+    });
+  }, [setSearchParams]);
+
+  return { resultId, openResult, closeResult };
+}

Using the functional updater form for setSearchParams also avoids closing over a potentially stale searchParams value if these are called in quick succession.

As per coding guidelines: "Use TypeScript with explicit types; prefer interface for public APIs".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts` around lines
3 - 20, The hook usePlanResultParam recreates openResult/closeResult each render
and lacks an explicit return type; fix by defining an exported interface (e.g.,
PlanResultParam) for the return shape and annotate usePlanResultParam():
PlanResultParam, and wrap openResult and closeResult with React.useCallback so
they are stable, using the functional updater form of setSearchParams inside
each callback to avoid closing over stale searchParams; reference the functions
usePlanResultParam, openResult, closeResult and setSearchParams when making
these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/web/app/routes/ws/deployments/page`.$deploymentId.plans.$planId.tsx:
- Around line 146-212: The dialog can open with an empty title because
PlanDiffDialog's open prop only checks resultId while results are still loading
or the id is invalid; update the component to only open the dialog when an
actual result is found or to auto-close when results finish loading with no
match: change the PlanDiffDialog props to use open={resultId != null &&
activeResult != null && !resultsQuery.isLoading} (or alternatively, add an
effect that calls closeResult() when resultsQuery.isLoading becomes false and
activeResult is undefined) and keep title={activeResult ?
resultTitle(activeResult) : ""}; reference PlanDiffDialog, activeResult,
resultId, resultsQuery, closeResult, and resultTitle to locate the change.

---

Nitpick comments:
In `@apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts`:
- Around line 3-20: The hook usePlanResultParam recreates openResult/closeResult
each render and lacks an explicit return type; fix by defining an exported
interface (e.g., PlanResultParam) for the return shape and annotate
usePlanResultParam(): PlanResultParam, and wrap openResult and closeResult with
React.useCallback so they are stable, using the functional updater form of
setSearchParams inside each callback to avoid closing over stale searchParams;
reference the functions usePlanResultParam, openResult, closeResult and
setSearchParams when making these changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 48cc6eda-8f14-466b-b828-9b11166579a7

📥 Commits

Reviewing files that changed from the base of the PR and between c5ed0d8 and 6680bcb.

📒 Files selected for processing (3)
  • apps/web/app/routes/ws/deployments/_components/plans/PlanDiffDialog.tsx
  • apps/web/app/routes/ws/deployments/_hooks/usePlanResultParam.ts
  • apps/web/app/routes/ws/deployments/page.$deploymentId.plans.$planId.tsx

@adityachoudhari26 adityachoudhari26 merged commit a7fdee7 into main Apr 22, 2026
17 checks passed
@adityachoudhari26 adityachoudhari26 deleted the open-single-plan-from-url branch April 22, 2026 22:39
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: ability to open a single target's plan from a url

2 participants