Skip to content

feat(deployment): add flag-gated redesigned deployment detail page - #3580

Merged
ygrishajev merged 1 commit into
mainfrom
feat/deployment-detail-redesign-shell
Aug 12, 2026
Merged

feat(deployment): add flag-gated redesigned deployment detail page#3580
ygrishajev merged 1 commit into
mainfrom
feat/deployment-detail-redesign-shell

Conversation

@ygrishajev

@ygrishajev ygrishajev commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Why

The deployment detail page opens on a dense grid of text (balance, cost, spent, status, time-left, dseq). The redesign leads with what a user cares about at a glance — is it running, where can I reach it, what does it cost, how much balance is left — and reorganizes the tabs. This PR lays the foundational shell every subsequent tab slice builds on, shipped behind feature flags in parallel with the current page so nothing changes for users until we deliberately roll it over.

Closes CON-821

What

A redesigned deployment detail page behind two Unleash flags:

  • deployment_detail_redesign — gates the canonical /deployments/[dseq] route (redesigned vs legacy page). Off → the current page is byte-for-byte unchanged.
  • deployment_detail_preview — gates a temporary, development-only /deployments/[dseq]/preview route that renders the same page for e2e and demos, while the canonical route keeps serving the legacy page. Both routes mount the identical DeploymentDetail component; removing the preview later is pure file deletion.

The page:

  • Header — app thumbnail (placeholder for now), running/status state, deployment name, live URL + Visit, and a summary card: total services, cost (hourly for GPU, monthly otherwise), account balance, auto top-up, and aggregate GPU / vCPU / memory / storage.
  • Tabs — Details · Logs · Events · Shell · Settings · Billing & Notifications. Tab bodies reuse the existing components (lease rows, logs, shell, manifest update, alerts) so nothing regresses. Tabs update the ?tab= query param via history.replaceState — no navigation.
  • Full-width layout with the design's #f5f5f5 content band and underline tabs.
  • Smoke e2e on the /preview route: deploy a container, then assert the redesigned header + full tab bar render and tabs update ?tab= without navigating.

The old page is renamed to DeploymentDetailLegacy so the redesign permanently owns the DeploymentDetail name and rollout teardown stays deletion-only (delete the legacy page, the preview route, and the flags).

Provisional, refined in later slices: header balance uses the account total; services count and live URL derive from the primary live lease; the thumbnail is a placeholder pending a product decision.

image

Summary by CodeRabbit

  • New Features

    • Added a redesigned deployment details experience with Details, Logs, Events, Shell, Settings, and Billing tabs.
    • Added deployment status, service links, resource and cost metrics, wallet balance, GPU usage, and auto top-up information.
    • Added URL-synchronized tabs, deployment error handling, and clearer inactive-deployment messaging.
    • Added a feature-flagged preview route with validation and fallback to the existing experience.
    • Improved GPU lease status detection.
  • Tests

    • Added coverage for deployment details, headers, feature-flag routing, preview behavior, lease states, and tab navigation.

@ygrishajev
ygrishajev requested a review from a team as a code owner August 11, 2026 12:13
@ygrishajev
ygrishajev force-pushed the feat/deployment-detail-redesign-shell branch from f4e0beb to 20b1c79 Compare August 11, 2026 12:13
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The deployment detail redesign adds feature-flagged routing, a data-driven detail page with URL-synchronized tabs, a metrics header, a gated preview route, shared GPU lease detection, and unit and Playwright coverage.

Deployment detail redesign

Layer / File(s) Summary
Feature flags and detail routing
apps/deploy-web/src/types/feature-flags.ts, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.tsx, apps/deploy-web/src/components/deployments/DeploymentDetailLegacy.tsx, apps/deploy-web/src/pages/deployments/[dseq]/index.tsx, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.spec.tsx
The deployment route selects the redesigned or legacy detail component through deployment_detail_redesign. Tests verify selection and dseq forwarding.
Deployment detail data and presentation
apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.tsx, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.spec.tsx, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx
The redesigned page loads deployment, lease, and provider data. It renders tabs, lease details, errors, manifests, billing, and inactive states. The header renders service, resource, cost, wallet, GPU, URI, and auto top-up data.
Shared GPU lease detection
apps/deploy-web/src/utils/reclamationUtils.ts, apps/deploy-web/src/utils/reclamationUtils.spec.ts, apps/deploy-web/src/components/deployments/DeploymentSubHeader.tsx
hasLiveGpuLease centralizes detection of active or reclaiming GPU leases. DeploymentSubHeader uses the shared helper.
Preview route and feature gating
apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.tsx, apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.spec.tsx, apps/deploy-web/src/pages/deployments/[dseq]/preview.tsx, apps/deploy-web/tests/ui/deployment-detail-preview.spec.ts
The preview route validates numeric dseq values. The preview component gates the redesigned page with deployment_detail_preview. Tests verify flag behavior, summary content, tabs, and Events query-parameter navigation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: stalniy, baktun14

✨ 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 feat/deployment-detail-redesign-shell

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

apps/deploy-web/src/components/deployments/DeploymentSubHeader.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 2 others

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

@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.97744% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.90%. Comparing base (b1c6c8f) to head (37a3df3).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../deployments/DeploymentDetail/DeploymentDetail.tsx 90.90% 6 Missing and 1 partial ⚠️
...eploy-web/src/pages/deployments/[dseq]/preview.tsx 0.00% 3 Missing ⚠️
...yments/DeploymentDetail/DeploymentDetailHeader.tsx 97.29% 1 Missing ⚠️
...src/components/deployments/DeploymentSubHeader.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3580      +/-   ##
==========================================
- Coverage   76.46%   75.90%   -0.57%     
==========================================
  Files        1137     1052      -85     
  Lines       29679    27461    -2218     
  Branches     7372     6969     -403     
==========================================
- Hits        22695    20843    -1852     
+ Misses       6151     5817     -334     
+ Partials      833      801      -32     
Flag Coverage Δ *Carryforward flag
api 89.04% <ø> (ø) Carriedforward from b1c6c8f
deploy-web 66.83% <90.97%> (+0.42%) ⬆️
log-collector ?
notifications 93.84% <ø> (ø) Carriedforward from b1c6c8f
provider-console 81.38% <ø> (ø) Carriedforward from b1c6c8f
provider-inventory ?
provider-proxy 88.17% <ø> (ø) Carriedforward from b1c6c8f
tx-signer ?

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...ments/DeploymentDetail/DeploymentDetailPreview.tsx 100.00% <100.00%> (ø)
...yments/DeploymentDetail/DeploymentDetailRouter.tsx 100.00% <100.00%> (ø)
.../components/deployments/DeploymentDetailLegacy.tsx 1.85% <100.00%> (ø)
.../deploy-web/src/pages/deployments/[dseq]/index.tsx 0.00% <ø> (ø)
apps/deploy-web/src/utils/reclamationUtils.ts 100.00% <100.00%> (ø)
...yments/DeploymentDetail/DeploymentDetailHeader.tsx 97.29% <97.29%> (ø)
...src/components/deployments/DeploymentSubHeader.tsx 8.33% <0.00%> (+8.33%) ⬆️
...eploy-web/src/pages/deployments/[dseq]/preview.tsx 0.00% <0.00%> (ø)
.../deployments/DeploymentDetail/DeploymentDetail.tsx 90.90% <90.90%> (ø)

... and 102 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.tsx`:
- Line 8: Update DeploymentDetail’s router usage to import useRouter from
next/router instead of next/navigation. Read router.query.tab only after
router.isReady, preserving the existing tab behavior once the Pages Router is
initialized.

In
`@apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx`:
- Around line 46-50: Update the leaseStatus construction in the hasLeaseStatus
branch to create mock<LeaseStatusDto>() without nested forwarded_ports or
services overrides, then assign forwarded_ports and services afterward as plain
objects using the existing values. Preserve the current service URI mapping and
return behavior.

In
`@apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.spec.tsx`:
- Around line 30-31: Update the setup function’s useFlag mock to use the typed
mock helper with typeof DEPENDENCIES.useFlag, then configure its return value
from input.isRedesignEnabled; remove the unsafe as unknown as cast.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2de96f09-2d3a-41d9-8752-81fa01b4785f

📥 Commits

Reviewing files that changed from the base of the PR and between 0fac091 and 20b1c79.

📒 Files selected for processing (11)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetailLegacy.tsx
  • apps/deploy-web/src/pages/deployments/[dseq]/index.tsx
  • apps/deploy-web/src/pages/deployments/[dseq]/preview.tsx
  • apps/deploy-web/src/types/feature-flags.ts
  • apps/deploy-web/tests/ui/deployment-detail-preview.spec.ts

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the inline findings, I also checked whether a freshly-loaded closed deployment would show a blank body on the redesigned page — isLeasesLoaded never resolves for non-active deployments since the lease query is gated with enabled: deployment?.state === "active", but that same gating pattern exists in the legacy page today, so it's not a regression introduced by this PR.

Extended reasoning...

Checked this specifically because the redesigned page's body render condition (deployment && isLeasesLoaded) looked suspicious for closed deployments, where the lease list query is disabled and isSuccess never flips true. Confirmed the legacy DeploymentDetailLegacy.tsx has the identical enabled: deployment?.state === "active" gating and the identical deployment && isLeasesLoaded render guard, so this behavior (if it is even a real issue) predates this PR and isn't introduced by the redesign.

@ygrishajev
ygrishajev force-pushed the feat/deployment-detail-redesign-shell branch from 20b1c79 to c50ee4a Compare August 11, 2026 13:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.spec.tsx`:
- Around line 21-28: Update the “tracks a navigate_tab analytics event when
switching tabs” test to also assert that window.location.search equals
"?tab=LOGS" after clicking the Logs tab, covering the tab query-parameter update
via history replacement while preserving the existing analytics and
rendered-content assertions.

In
`@apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.spec.tsx`:
- Around line 24-29: Update setup’s useFlag in DeploymentDetailPreview tests to
be a spy that returns input.isPreviewEnabled, expose that spy from setup, and
assert it is called with "deployment_detail_preview" in the relevant test.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7adb87c5-06e0-4902-b742-32789c01aceb

📥 Commits

Reviewing files that changed from the base of the PR and between 20b1c79 and c50ee4a.

📒 Files selected for processing (8)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.spec.tsx
  • apps/deploy-web/src/types/feature-flags.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailRouter.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailPreview.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetail.tsx
  • apps/deploy-web/src/types/feature-flags.ts

@ygrishajev
ygrishajev force-pushed the feat/deployment-detail-redesign-shell branch 2 times, most recently from 547009f to a8b768c Compare August 12, 2026 09:54
@ygrishajev
ygrishajev enabled auto-merge August 12, 2026 09:58
Introduce a redesigned deployment detail page behind two Unleash flags, alongside the current one.

- `deployment_detail_redesign` gates the canonical `/deployments/[dseq]` route (new vs legacy page).
- `deployment_detail_preview` gates a temporary parallel `/deployments/[dseq]/preview` route that
  renders the same page for e2e and demos while the canonical route keeps serving the legacy page.

The new page leads with a status-first header (thumbnail, running state, live URL, and a summary
card: services, cost, balance, auto top-up, aggregate GPU/vCPU/memory/storage) and a six-tab bar
(Details, Logs, Events, Shell, Settings, Billing & Notifications). Tab bodies reuse the existing
components so nothing regresses, and tabs update the `?tab=` param without navigating.

Rename the old page to `DeploymentDetailLegacy` so the redesign owns the `DeploymentDetail` name,
keeping rollout teardown deletion-only.
@ygrishajev
ygrishajev force-pushed the feat/deployment-detail-redesign-shell branch from a8b768c to 37a3df3 Compare August 12, 2026 10:54
@ygrishajev
ygrishajev disabled auto-merge August 12, 2026 10:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/deploy-web/src/utils/reclamationUtils.spec.ts (1)

149-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the reclaiming live-lease branch.

hasLiveGpuLease treats both active and reclaiming leases as live. The suite only exercises active and closed. Add a positive reclaiming case so a regression in the second live state fails the test.

Suggested test
     it("is true when a live lease has a GPU", () => {
       expect(hasLiveGpuLease([createLease({ state: "active", gpuAmount: 1 })])).toBe(true);
     });
+
+    it("is true when a reclaiming lease has a GPU", () => {
+      expect(hasLiveGpuLease([createLease({ state: "reclaiming", gpuAmount: 1 })])).toBe(true);
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/deploy-web/src/utils/reclamationUtils.spec.ts` around lines 149 - 160,
Add a positive test in the hasLiveGpuLease suite using createLease with state
"reclaiming" and a positive gpuAmount, asserting the result is true. Keep the
existing active, closed, and zero-GPU coverage unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/deploy-web/src/utils/reclamationUtils.spec.ts`:
- Around line 149-160: Add a positive test in the hasLiveGpuLease suite using
createLease with state "reclaiming" and a positive gpuAmount, asserting the
result is true. Keep the existing active, closed, and zero-GPU coverage
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c0f4a0c7-3031-4f3b-b6eb-56697db601ba

📥 Commits

Reviewing files that changed from the base of the PR and between a8b768c and 37a3df3.

📒 Files selected for processing (5)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx
  • apps/deploy-web/src/components/deployments/DeploymentSubHeader.tsx
  • apps/deploy-web/src/utils/reclamationUtils.spec.ts
  • apps/deploy-web/src/utils/reclamationUtils.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.tsx
  • apps/deploy-web/src/components/deployments/DeploymentDetail/DeploymentDetailHeader.spec.tsx

@ygrishajev
ygrishajev added this pull request to the merge queue Aug 12, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beyond the inline nits found this run, I also checked two other candidates and ruled them out: the COST tile summing lease prices without checking denom mirrors the same pre-existing pattern already used by DeploymentSubHeader/useDeploymentMetrics, not a new risk introduced here; and the header's lease status still refreshes on window refocus/remount even without the Details tab mounted, so it does not go permanently stale the way the earlier manifest-resend refresh issue does.

Extended reasoning...

This is a follow-up note on top of the four nit-level findings already posted inline this run (deploymentCost/hasGpu duplication between DeploymentDetailHeader and DeploymentSubHeader/useDeploymentMetrics, the TOTAL SERVICES fallback counting historical leases, and the closeManifestEditor tab/URL desync). Two additional candidates surfaced by finder agents were investigated and refuted: (1) the COST tile summing lease.price.amount across leases without checking denom — this is not new behavior, it is the same computation useDeploymentMetrics already performs for the legacy DeploymentSubHeader, so it carries no additional risk from this PR; (2) whether the header's own useLeaseStatus call goes stale when the Details tab (and its polling LeaseRow) is not mounted — it still refetches on window focus and component remount by default TanStack Query behavior, so it degrades gracefully rather than freezing indefinitely. Recording this so a later pass on this PR does not need to re-derive the same analysis. This is informational only and does not change the fact that a human should still review the substantive findings already posted inline.

export const DeploymentDetailHeader: FC<DeploymentDetailHeaderProps> = ({ deployment, leases, providers, dependencies: d = DEPENDENCIES }) => {
const { deploymentLocalStorage } = d.useServices();
const { address } = d.useWallet();
const deploymentCost = leases?.reduce((sum, lease) => sum + parseFloat(lease.price.amount), 0) ?? 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 DeploymentDetailHeader.tsx:47 reimplements the same lease-price reduce already defined in useDeploymentMetrics.tsx:18 (leases.reduce((p,c) => p + parseFloat(c.price.amount), 0)) instead of extracting a shared getDeploymentCost(leases) helper. Both DeploymentSubHeader (via useDeploymentMetrics) and DeploymentDetailHeader now carry the identical formula independently for the duration of the flag-gated rollout — the same duplication pattern this PR already fixed for the GPU predicate via hasLiveGpuLease in reclamationUtils.ts.

Extended reasoning...

What the duplication is: useDeploymentMetrics.tsx:18 computes deploymentCost as leases.reduce((prev, current) => prev + parseFloat(current.price.amount), 0). The new DeploymentDetailHeader.tsx:47 (added by this PR) reimplements the identical formula inline: leases?.reduce((sum, lease) => sum + parseFloat(lease.price.amount), 0) ?? 0. Both compute the exact same domain value — total lease cost as a sum of lease prices — differing only in null-guard style (?? 0 vs. relying on the hook's non-null leases param) and variable names.

Why the header avoided the hook in the first place: this PR's own review thread already flagged that pulling in useDeploymentMetrics for this single field is wasteful, because that hook also unconditionally calls useRealTimeLeftuseBlock(\"latest\", { refetchInterval: 30000 }), opening a standing 30s chain-API poll whose result (realTimeLeft) DeploymentDetailHeader never uses. The fix applied was exactly what's flagged here: compute deploymentCost inline instead of calling the hook. That's the right call for avoiding the poll, but it reintroduces a second, independent copy of the cost formula that DeploymentSubHeader (via useDeploymentMetrics) also carries.

Why this matters during the rollout: per the PR description, DeploymentSubHeader/DeploymentDetailLegacy and DeploymentDetailHeader/DeploymentDetail coexist in production behind the deployment_detail_redesign flag until the legacy page is deleted. Any future change to the cost formula (e.g. handling a different price denom, adding a discount, rounding rule) has no single point of update — a maintainer editing one file gets no compiler or test signal that the sibling copy also needs the same edit.

Precedent already set in this very PR: the analogous GPU-lease predicate (leases?.some(l => isLeaseLive(l) && l.gpuAmount && l.gpuAmount > 0)) was duplicated in exactly the same way between DeploymentSubHeader and DeploymentDetailHeader, and this PR extracted it into a shared hasLiveGpuLease(leases) helper in reclamationUtils.ts, with both headers now calling it (see DeploymentSubHeader.tsx diff and DeploymentDetailHeader.tsx:60). The cost formula is the same shape of problem and would benefit from the same treatment: a small pure getDeploymentCost(leases) helper (e.g. colocated with useDeploymentMetrics or alongside hasLiveGpuLease) that both useDeploymentMetrics and DeploymentDetailHeader call.

Step-by-step proof of the duplication:

  1. useDeploymentMetrics.tsx:18: leases.reduce((prev, current) => prev + parseFloat(current.price.amount), 0).
  2. DeploymentDetailHeader.tsx:47: leases?.reduce((sum, lease) => sum + parseFloat(lease.price.amount), 0) ?? 0.
  3. Both expressions sum the same field (price.amount, parsed as a float) across the same collection (leases) for the same purpose (total lease cost feeding PricePerTimeUnit/the COST tile).
  4. The two host components are mutually exclusive at render time (flag-gated) but coexist in the codebase for the duration of the rollout, so a fix or behavior change to one copy silently fails to propagate to the other, with nothing catching the drift.

Impact: this is a pure reuse/code-quality issue — no incorrect behavior, crash, or data loss results from the current duplication, so it does not block merging this PR. It's raised because the fix is small, well-precedented within this same diff, and worth doing before the duplication has two independent lifetimes to track during the rollout.

Merged via the queue into main with commit be98b7e Aug 12, 2026
58 checks passed
@ygrishajev
ygrishajev deleted the feat/deployment-detail-redesign-shell branch August 12, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants