chore(site): migrate all `<Dialog />`s off MUI by jakehwll · Pull Request #27506 · coder/coder · GitHub
Skip to content

chore(site): migrate all <Dialog />s off MUI - #27506

Merged
jakehwll merged 19 commits into
mainfrom
jakehwll/demui-dialogs
Jul 31, 2026
Merged

chore(site): migrate all <Dialog />s off MUI#27506
jakehwll merged 19 commits into
mainfrom
jakehwll/demui-dialogs

Conversation

@jakehwll

@jakehwll jakehwll commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

🤖 This PR was modified by Coder Agents on behalf of Jake Howell.

Removes Material UI from every dialog, moving them onto the internal shadcn/radix Dialog primitives. After this change there are no @mui/material/Dialog usages left in site/src.

Changes

  • Consolidated components/Dialogs/*components/Dialog/* and folded the old ConfirmDeleteDialog into ConfirmDialog (type="delete").
  • Rewrote ConfirmDialog, DeleteDialog, WorkspaceDeleteDialog, ScheduleDialog, and AnnouncementBannerDialog onto the internal primitives (native Input/Label/Checkbox/Link instead of MUI).
  • Finished the migration for the last two MUI hold-outs: UpdateBuildParametersDialog and MissingTemplateVariablesDialog.
  • Dialog prop types now compose the rendered component's props (ComponentProps<typeof Dialog> / MUI DialogProps) instead of hand-rolled { open; onOpenChange } shapes.

Testing

AI-Driven manual dogfood sweep in a live instance (premium license), driven in a browser. Each dialog checked for logical rendering (layout, variant styling, buttons, no overlap/blank/console error) and function (open, primary action, cancel/close, guard states):

Dialog / surface How tested Result
ConfirmDialog (delete / info / success) Token delete, update-confirm, change-version
DeleteDialog (type-to-confirm) Delete user, group, license, provider, OAuth2 app
WorkspaceDeleteDialog Workspace actions → Delete (+ orphan path via failed workspace)
ScheduleDialog Template schedule → dormancy/deletion warning
AnnouncementBannerDialog Deployment → Appearance → New banner (live preview + color)
ChangeWorkspaceVersionDialog Workspace actions → Change version
DownloadLogsDialog Workspace actions → Download logs
Batch delete (workspaces) Workspaces list → multi-select → Delete
TemplatePageHeader delete Template → Delete (cancelled)
FileDialog (create/rename/delete) Template editor file tree
MissingTemplateVariablesDialog (migrated) Editor → add variable → Build
PublishTemplateVersionDialog Editor → Publish
UpdateBuildParametersDialog (migrated) Classic flow + required param → workspace Update (renders + submits)
Update-confirmation (WorkspaceUpdateDialogs) Workspace Update
Suspend/activate confirm Users → member row
ResetPasswordDialog Users → member → Reset password
Token delete confirm Settings → Tokens
Create-token confirm Token create flow
SSH key regenerate confirm Settings → SSH Keys
Change-login-type confirm Settings → Security
Secret delete Settings → Secrets
Group delete Admin → Groups
Org member remove Admin → Organization → Members
License remove Deployment → Licenses (cancelled)
Announcement banner delete Deployment → Appearance
OAuth2 app delete Deployment → OAuth2 apps
ModelFormDialogs (form + delete) AI → Models
Provider delete AI → Providers
Gateway key create/delete AI → Gateway keys
MCPServerFormDialogs delete AI → MCP servers
Personal skill (create form + delete) Agents → Personal Skills
Spend user-override (add + delete) AI → Spend

Human tested:

  • template version promote/archive
  • external-auth/OAuth2-provider delete
  • custom-role delete
  • cancel-provisioner-job

Things that have a chance to bleed:

  • tasks dialogs
  • dormant inline confirm

Should be known that each of these renders through the already-verified ConfirmDialog/DeleteDialog, so the underlying component is covered even where the specific trigger wasn't reachable.

Plan & decision log

Goal: finish the de-MUI migration everywhere and confirm every affected modal renders logically and functions.

Phase 1 - complete the migration

  • Confirmed only two files still rendered MUI Dialog (UpdateBuildParametersDialog, MissingTemplateVariablesDialog); ported both to the internal primitives, preserving the { open, onClose, ... } public API (mapped to onOpenChange internally) so call sites were unchanged. radix now wires aria-labelledby/aria-describedby, removing a duplicated element id.

Phase 2 - sighting sweep (live browser, premium license)

  • Batch A (workspaces/tasks): 4 PASS, rest state-gated.
  • Batch B (templates): MissingTemplateVariablesDialog, FileDialog, PublishTemplateVersionDialog, template delete - all PASS.
  • UpdateBuildParametersDialog: reached by enabling classic parameter flow + pushing a version with a required parameter - PASS (renders + submits).
  • Batch C (users/org/settings): 10 PASS.
  • Batch D (deployment/AI): 10 PASS.

Decisions

  • Kept ConfirmDialog-wrapper prop types explicit (composing DialogProps there reintroduced a MUI smell).
  • Dropped the unused ConfirmDialogType export (knip) and updated the WorkspacePage orphan-delete test: the radix Checkbox puts the test id on the role=checkbox button itself, so the previous within(...).getByRole no longer matched.

jakehwll added 5 commits July 27, 2026 03:45
The dialog de-MUI branch had accidentally downgraded vite from 8.0.16 to
8.0.10 (with the corresponding pnpm-lock churn) via an unrelated change.
Restore site/package.json and site/pnpm-lock.yaml to match main.
…ponent

Replace hand-rolled inline { open; onOpenChange? } / { open; onClose } prop
types with composition of the underlying dialog's props:
- TaskFeedbackDialog composes ComponentProps<typeof Dialog>.
- UpdateBuildParametersDialog and MissingTemplateVariablesDialog compose
  MUI DialogProps, matching the components they still render.

This restores the prior DialogProps-style composition and forwards the full
dialog prop surface instead of a fixed subset.
- Stop exporting ConfirmDialogType; it is only used within ConfirmDialog,
  and the dangling export failed knip (lint job).
- Update the WorkspacePage orphan-on-delete test to select the orphan
  checkbox by test id. The migrated radix Checkbox sets the test id on the
  role=checkbox button itself, so the previous within(...).getByRole did not
  match (test-js job).
UpdateBuildParametersDialog and MissingTemplateVariablesDialog were the only
remaining users of MUI Dialog in site/src. Port them to the internal Dialog
primitives (DialogContent/Header/Title/Description/Footer + Button), keeping
the form submit semantics, the loading state, and the public
{ open, onClose, ... } API so call sites are unchanged. radix now wires
aria-labelledby/aria-describedby from the title/description, removing the
duplicated update-build-parameters-title id.
Revert the earlier 8.0.16 restore. vite 8.0.16 breaks the local dev site;
8.0.10 is the working version for this branch. The 8.0.16 bump is handled
on a separate branch.
@jakehwll jakehwll changed the title fix: demui <Dialog /> (globally) refactor(site): migrate all dialogs off Material UI Jul 27, 2026
@jakehwll jakehwll changed the title refactor(site): migrate all dialogs off Material UI refactor(site): migrate all <Dialog />s off MUI Jul 27, 2026
@jakehwll jakehwll changed the title refactor(site): migrate all <Dialog />s off MUI chore(site): migrate all <Dialog />s off MUI Jul 27, 2026
Restore site/package.json and site/pnpm-lock.yaml to match main. The vite
version bump is handled on a separate branch and does not belong in the
dialog migration.
@jakehwll jakehwll changed the title chore(site): migrate all <Dialog />s off MUI refactor(site): migrate all dialogs off Material UI Jul 27, 2026
@jakehwll
jakehwll marked this pull request as ready for review July 27, 2026 08:10

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1086bb2211

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog.tsx Outdated
…oxes

Constrain the migrated parameter dialogs to the viewport and make their
form regions scrollable so the footer stays reachable for long parameter
and variable lists.

Feed the schedule dialog checkboxes their checked state so the check
icon reflects the selection, and add interaction coverage that exercises
the prevention options, submit, and cancel flows.
Comment on lines +14 to +15
const warnBoxClassName =
"mt-6 flex gap-2 rounded-lg border border-solid border-border-warning bg-surface-orange p-3 leading-snug text-content-warning";

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.

🥲 If we're re-using this twice could we not just make a little component or something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Honestly, this needs some design attention 😅 We've got a backlog ticket for it, I think I might leave it for then...

@jakehwll

This comment was marked as outdated.

@coder-agents-review

This comment was marked as outdated.

@jakehwll jakehwll changed the title refactor(site): migrate all dialogs off Material UI chore(site): migrate all <Dialog />s off MUI Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-27 12:07 UTC by @jakehwll
Spend: $65.13 / $100.00

Review history
  • R1 (2026-07-27): 15 reviewers, 4 Nit, 5 Note, 3 P3, 1 P4, COMMENT. Review
  • R2 (2026-07-27): 13 reviewers, 4 Nit, 5 Note, 3 P3, 1 P4, APPROVE. Review

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review 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.

This is a clean, well-executed migration. It removes @mui/material/Dialog from all of site/src (verified: no @mui/material/Dialog imports and no components/Dialogs/ references remain), folds ConfirmDeleteDialog into ConfirmDialog type="delete" consistently across callers, and the deleted RTL tests for ConfirmDialog/DeleteDialog were re-expressed as story play functions that cover more than the originals. ScheduleDialog's checkboxes moved from uncontrolled MUI inputs to controlled radix Checkboxes wired to form values, closing a latent visual/state desync. Pariston tried to build a case against the premise and could not; Hisoka put it best: "I came to fight the migration and it fought back clean."

No P0-P2 findings. Severity: 3 P3, 1 P4, 4 Nits, 5 Notes.

The P3s cluster on two themes. Coverage: the two dialogs rewritten wholesale (UpdateBuildParametersDialog, MissingTemplateVariablesDialog) ship with no story or test, while every other rewrite in the PR gained play coverage. UpdateBuildParametersDialog carries formik + Yup + a submit path, so "manually dogfooded once" is the only proof its submit works, and site/AGENTS.md mandates story coverage as the test mechanism. The authoring agent will not return to add it, so this is a human's call, not a silent gap. Accessibility/convention: ScheduleDialog and AnnouncementBannerDialog render a DialogContent with a title but no DialogDescription and without the aria-describedby={undefined} opt-out that this PR's own ConfirmDialog uses, so radix emits its dev accessibility warning on open (the "no console error" bar the PR claims) and leaves a dead aria-describedby reference. And ConfirmDialog's rewrite dropped the doc that warned callers a type="delete" dialog with no onConfirm silently closes without deleting.

The Nits and Notes cover shared-primitive reuse (ConfirmDialog hand-rolls the DialogActions footer), a danger to warning (red to orange) color shift on WorkspaceDeleteDialog's warning boxes that reads as a design decision bundled into a mechanical PR, and duplicated scrollable-form scaffold worth watching if a third such dialog appears.

Process: the PR description is a model diagnosis (goal, per-phase reasoning, non-obvious decisions, and the surfaces that could bleed), and the WorkspacePage test change is a real root-cause fix (the radix Checkbox carries the testid on the role=checkbox element), verified passing. CI is green (16 passed, 14 skipped).

🤖 This review was automatically generated with Coder Agents.

Comment thread site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog.tsx Outdated
Comment thread site/src/pages/TemplateSettingsPage/TemplateSchedulePage/ScheduleDialog.tsx Outdated
Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.tsx
Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.tsx
Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.tsx Outdated
Comment thread site/src/pages/TemplateVersionEditorPage/MissingTemplateVariablesDialog.tsx Outdated
…migration

- ScheduleDialog and AnnouncementBannerDialog set aria-describedby={undefined}
  so radix does not point at a missing description or warn on open (CRF-4).
- ConfirmDialog documents that onConfirm falls back to onClose (a delete
  dialog without it closes without deleting) and reuses the shared
  DialogActions footer instead of a hand-rolled one (CRF-5, CRF-6, CRF-11).
- Add story play coverage for the wholesale-rewritten UpdateBuildParametersDialog
  and MissingTemplateVariablesDialog, and Update/Cancel coverage for
  AnnouncementBannerDialog (CRF-1, CRF-3, CRF-13).
- Restore the destructive (red) styling on WorkspaceDeleteDialog's task and
  orphan warning boxes that the migration had softened to orange (CRF-7).
- MissingTemplateVariablesDialog uses Array.prototype.with instead of
  mutate-then-copy, and the AnnouncementBannerDialog preview comment now
  carries intent (CRF-14, CRF-10, CRF-12).

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review 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.

Round 2 re-review. The fix commit (4ae7a3d) is a model correction pass: it maps each change to its finding, restores rather than rewrites, and stays proportional (128 insertions for 11 findings, no scope drift). Every previously-open finding that called for a code change is fixed at root cause, verified by the panel against the components and fixtures rather than the commit labels:

  • CRF-1 / CRF-3 / CRF-13 (untested rewritten dialogs): UpdateBuildParametersDialog, MissingTemplateVariablesDialog, and AnnouncementBannerDialog now ship stories whose play functions type into the real inputs and assert the submit payloads (onUpdate/onSubmit) and cancel paths (onClose/onCancel). Bisky and Mafu-san confirmed these are real submit-path assertions, not render-only shells.
  • CRF-4 (aria-describedby opt-out): closed as a class, not just the two flagged files. Mafu-san grepped every changed dialog; each now either renders a DialogDescription or passes aria-describedby={undefined}.
  • CRF-5 / CRF-6 / CRF-11 (ConfirmDialog): the onConfirm-fallback doc is restored and names the consequence, the footer now reuses the shared DialogActions helper (safe because the dialog mounts no form and radix portals the content), and the hideCancel doc is one line.
  • CRF-7 (color regression): the destructive (red) tokens are restored on both warning boxes and the orphan checkbox, matching the pre-migration render.
  • CRF-14 (mutate-then-copy): now prev.with(index, ...). Hisoka and Meruem traced that the useEffect seeds the array to full length before any onChange can fire, so .with cannot throw.

Netero re-ran with tsc, knip, and biome all green, and none of the 13 panel reviewers raised a new finding. Komugi cleared the new play functions as deterministic (no timers, no unseeded entropy, single-checkbox queries unambiguous). Hisoka summed it up: "Boring, careful code. Silence is the rating it earned."

Two Notes remain acknowledged without a code change and without a tracking ticket: CRF-8 (DeleteDialog resetConfirmation fires only on a user-initiated close, harmless today because every reuse-across-entities caller keys the instance) and CRF-9 (the scrollable-form scaffold is duplicated across the two form dialogs, to be folded when a third appears). The panel re-verified both and found no new evidence to re-raise, and both match the reviewers' own not-worth-changing-now disposition. Neither is agent-acceptable as permanent: a maintainer should either file a tracking issue or explicitly accept them as won't-fix.

Note: the storybook vitest project could not be executed in the review worktree (init_emotion_react_browser_development_esm, a vitest/browser version mismatch in the harness, unrelated to this PR), so the story verdicts above are by inspection against the components and fixtures; PR CI reports the storybook project green.

🤖 This review was automatically generated with Coder Agents.

jakehwll added 3 commits July 27, 2026 12:34
The prior fix changed the task and orphan warning boxes to destructive
(red) tokens on the assumption that the pre-migration warnContainer was
red. theme.roles.danger is actually defined entirely in orange tones
(colors.orange[*] for background, outline, text, and fill.solid), so the
migration's border-warning/surface-orange/content-warning mapping already
matched the original render. Restore it.
Explain that radix's Dialog.Content spreads consumer props after its own
aria-describedby={descriptionId}, so passing aria-describedby={undefined}
when there is no DialogDescription drops the dangling reference and
silences radix's dev warning. Simplify the conditional spread to
{...(!description && { "aria-describedby": undefined })}.
Comment on lines +92 to +94
// Radix defaults aria-describedby to a descriptionId that dangles
// (and warns) without a DialogDescription, so opt out when absent.
{...(!description && { "aria-describedby": undefined })}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Image

Im happy to chalk this up to being too defensive against Radix. We have no instances of a <ConfirmDialog /> currently that lacks a description. However, I'm for this with our future accessibility work in mind!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is so nasty it almost makes me wonder if description just shouldn't be optional

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.

fwiw I feel like quoting Radix's docs would be clearer than the current comment about dangling(???)

If you want to remove the description entirely, remove this part and pass aria-describedby={undefined} to Dialog.Content.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 6cba6a6. Made description required on ConfirmDialog and dropped the aria-describedby={undefined} guard entirely, so radix always wires to a real DialogDescription and the "nasty" opt-out is gone. tsc confirms every existing caller already passes a description, so no call sites changed. This also resolves @aqandrew's note, since the dangling-describedby comment no longer exists.

🤖 Replied by Coder Agents on behalf of Jake Howell.

Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.stories.tsx Outdated
Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.stories.tsx Outdated
Comment thread site/src/components/Dialog/ConfirmDialog/ConfirmDialog.tsx
}}
>
<DialogContent
variant={type === "delete" ? "destructive" : "default"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ok but the annoying thing here is that we also use "confirm" for delete dialogs all over the place. this component and the way we use it is a total mess.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed the confirmText/delete-button semantics across callers are messy. That's broader than this migration's scope (mechanical de-MUI), so leaving it for the dialog design cleanup rather than reshaping the API here. Flagging for that follow-up.

🤖 Replied by Coder Agents on behalf of Jake Howell.

Comment thread site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog.tsx Outdated
Comment on lines +184 to +191

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why are we not using <ErrorAlert> here? :despair:

...and then I realize this is in AgentsPage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correction to my last reply: this DeleteSkillDialog was migrated in this PR (ConfirmDeleteDialogConfirmDialog), so it is in scope. It keeps the Alert/AlertDescription pattern the surrounding AgentsPage code already uses rather than ErrorAlert, matching that page's local convention. Happy to switch it to ErrorAlert if you'd prefer consistency with the rest of the app, just say the word.

🤖 Replied by Coder Agents on behalf of Jake Howell.

jakehwll and others added 4 commits July 28, 2026 05:19
Replace the within(canvasElement.ownerDocument.body) queries in the
ConfirmDialog story play functions with storybook's screen, which already
targets the portaled dialog in document.body, per review feedback.
…itive

Bake max-h-[90vh] and overflow-y-auto into DialogContent so tall dialogs
scroll within the viewport without each consumer hand-rolling a max height
and a grid/overflow scaffold. Drop the one-off
max-h-[90vh] grid-rows-[...] plus inner overflow wrappers from
UpdateBuildParametersDialog and MissingTemplateVariablesDialog.

Also clarify the ConfirmDialog onConfirm doc (onClose doubles as the
confirm handler when omitted) and restore the shorter banner-preview
comment.
# Conflicts:
#	site/src/modules/workspaces/WorkspaceMoreActions/UpdateBuildParametersDialog.tsx
#	site/src/modules/workspaces/WorkspaceUpdateDialogs.tsx
jakehwll and others added 2 commits July 30, 2026 05:18
# Conflicts:
#	site/src/pages/DeploymentSettingsPage/OAuth2AppsSettingsPage/EditOAuth2AppPageView.tsx
@jakehwll
jakehwll enabled auto-merge (squash) July 31, 2026 03:19
@jakehwll
jakehwll merged commit 79724ab into main Jul 31, 2026
26 checks passed
@jakehwll
jakehwll deleted the jakehwll/demui-dialogs branch July 31, 2026 03:23
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants