refactor(dashboard): use shared url constructors for deploy routes#6393
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — one minor operator-label inconsistency inline.
Reviewed changes — a dashboard-wide refactor that routes deploy (and many adjacent) hrefs through a new typed route-builder layer and enables Next typedRoutes.
- Add
buildRoute+routes.projects.*builders —lib/navigation/routes/{shared,projects,index}.tssubstitute[segment]brackets from Next's generatedParamMap, validate the pattern against theAppRoutesliteral union (typo'd/removed routes fail to compile), and append queries viawithQuery. - Make
withQuerygeneric —lib/navigation/url.tspreserves the base's template-literal type so builder outputs stay typed. - Enable
typedRoutes—next.config.jsflipstypedRoutes: true;package.jsontypecheck becomesnext typegen && tsc. - Swap ~50 call sites — sidebar/leaves, breadcrumbs, crumbs, deployment rows/dialogs/banners, OAuth + Stripe + auth redirects move from hand-built template-literal urls to
routes.*calls, withas Routecasts added at sinks that accept arbitrary strings (behaviorally no-ops).
Behavior is preserved across the call-site swaps I traced. The one thing worth a glance is the logs deployment-filter operator flipping from is to contains (inline) — confirmed cosmetic, not a results regression.
Claude Opus | 𝕏
mcstepp
left a comment
There was a problem hiding this comment.
future suggestion: lib/navigation/types.ts keeps href: string even though leaves.ts now returns typed Route values. tightening this to Route (or a union) would propagate type safety into the sidebar without needing as Route casts in nav-row.tsx.
yep i agree |
ae5d7d0 to
2ecde71
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (ae5d7d0): a fix for the logs deployment-filter operator the last review flagged, plus a merge of main.
- Unify the deployment-filter operator on
is:—containsFilteris renamed toisFilterinlib/navigation/routes/projects.ts, so bothroutes.projects.logsandroutes.projects.requestsnow emitdeploymentId=is:${id}. This restores the logs link to its pre-PRis:and aligns the requests link with the requests/logs deployment-filter UI, which itself emitsoperator: "is". The change is functionally inert —use-sentinel-logs-query.ts:50-52and the runtime-logs query both discard the operator and match the id exactly — so only the filter-chip label is affected, and it now matches what manual UI filtering produces. - Drop the inert
appIdparam from the logs table action —deployment-list-table-action.popover.constants.tsxno longer forwardsappIdon the runtime-logs link (the page never parsed it). - Update
projects.test.ts— the deployment-id assertion now expectsis:forrequests. - Merge
main—b8dbffaccpulls in an unrelatedopenapi_specsunique-index migration and an audit-filter button style tweak; both sit outside this PR's refactor scope.
Prior review feedback is addressed cleanly and the merge introduced no conflicts in the touched route files.
Claude Opus | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the delta since the prior pullfrog review (2730839): the final commit 9b122e5 swaps the last batch of hand-rolled URLs to the routes.* builders and adds one UX hardening to the create-project flow.
- Swap remaining hand-rolled URLs to
routes.*—delete-project.tsx,delete-app.tsx,project-actions.tsx(3 links),project-card.tsx, and thecustom-domains/add.tsCONFLICT error message all move from template-literal paths to typed builders. Each maps 1:1 to the prior string. - Harden the workspace lookup in delete dialogs —
delete-project.tsx/delete-app.tsxswitch fromuseWorkspace()+workspace?.slugtouseWorkspaceNavigation()+workspace.slug; the new hook suspends/redirects on a null workspace, so this also removes a latent/undefined/...path during loading. - Keep the create-project dialog up during navigation —
create-project-dialog.tsxaddsuseTransition, drops the immediateonOpenChange(false), navigates insidestartNavigation, and blocks dialog-close while submitting or navigating so the redirect isn't orphaned and the user isn't left on the old page.
The route swaps are behaviorally inert (or a small improvement in the delete dialogs), and the create-project dialog change is correct and intentional — the component unmounts once the new route mounts. The prior is:/contains: operator thread was already resolved and acknowledged.
Claude Opus | 𝕏

NOTE
This PR enables type-safe routes so we can detect wrong routes during the build phase. This buys us type safety for
router.push,redirect, etc. The downside is that it's not super smart by default, so it thinks some routes don't exist. There is a way to fix that, and we already do proper typechecking inroutes/projects.ts. In the future, every URL in the codebase will adopt that structure, but for now we just assert withas Routeto overcome it. Nothing changed behaviorally; we're just making triangle.js happy.What we actually gained from this:
https://github.com/user-attachments/assets/b9c552c8-2d08-442b-b951-dc8fda6056ad
Motivation
This is the continuation of #6389. And this will make deploy routes type safe and robust
How to Test
mise exec -- pnpm --dir=web/apps/dashboard vitest run lib/navigation/routes=> Should passOnboarding
?step=/?appId=).Project sidebar
/{ws}/projects/{projectId}App sidebar
/{ws}/projects/{projectId}/apps/{appId}/...(deployments, settings, etc).Top nav breadcrumbs
Deployment links
/{ws}/projects/{projectId}/apps/{appId}/deployments/{deploymentId}?build=true./deployments(was 404ing).Query-param links (highest risk — confirm the query survives)
?deploymentId=contains:{deploymentId}?appId={appId}?since=/?appId=?from={x}&to={y}Lists
resource-card.tsx→ project list and app list both navigate.GitHub OAuth callback (touched, easy to miss)