Migrate dialogs to the universal store-based modal system
- Dominant language
- JavaScript
- Stars
- 400
- Forks
- 89
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 149
Description
We have a growing problem with how dialogs are rendered across the frontend. The current dominant pattern (component-ref based dialogs using `ff-dialog`) always renders the full dialog DOM tree on mount, hidden via CSS class toggling. Every page that uses dialogs has them sitting in the DOM at all times, teleported to ``, just invisible.
This gets worse when dialog-bearing components are rendered inside loops. We have confirmed cases where this results in tens or potentially hundreds of hidden dialog instances in the DOM:
* `BlueprintTile` contains an `AssetDetailDialog` and is rendered in nested v-for loops across 4 pages (blueprint library, blueprint selection, admin blueprints, instance creation flow). 50 blueprints = 50 hidden dialogs.
* `PipelineStage` contains a `DeployStageDialog` and is rendered per-stage per-pipeline. 5 pipelines with 4 stages = 20 hidden dialogs.
Beyond the DOM bloat, the ref-based pattern also creates tight coupling. Every parent that needs a dialog has to import it, mount it in the template, and manage a ref. The same dialog used in two places means duplicated imports, template entries, and refs.
## The universal system
We already have a store-based universal dialog system that solves all of this:
* **Store** (`stores/ux-dialog.js`): Manages dialog state, callbacks, and dynamic component config
* **Service** (`services/dialog.js`): Exposes `Dialog.show()` and `Dialog.showAsync()` for triggering from anywhere
* **Renderer** (`components/dialogs/PlatformDialog.vue`): Mounted once in the `Platform.vue` layout, dynamically renders whatever component the store holds via ``
This means one dialog container in the DOM, ever. Content renders on demand. No iteration multiplication. No coupling between parent and dialog. The right drawer system (`ux-drawers.js` + `RightDrawer.vue`) follows the exact same pattern and is already widely adopted.
Only ~4 places currently use the universal system vs ~54 using the old ref pattern.
## Scope
The migration covers roughly 49 standalone dialog component files and ~9 inline `ff-dialog` usages across the frontend:
* **Team section**: 15 dialog files
* **Admin section**: 7 dialog files
* **Instance section**: 6 dialog files
* **Device section**: 5 dialog files
* **Application section**: 2 dialog files
* **Account section**: 2 dialog files
* **Shared components**: 12 dialog files
* **Inline dialogs**: ~9 across various pages
## Technical considerations
Moving dialogs to the universal system means they become standalone content components passed dynamically to `PlatformDialog`. This raises a few things we need to formalize before we start migrating:
* **Dialog component contract**: What should a "universal-ready" dialog component look like? Right now the few components that work with `Dialog.show()` (like `AddDeviceToGroupDialog`) reach into the store directly for things like `setDisablePrimary()`. We should provide a cleaner interface, likely a composable (something like `useDialogContent()`) that gives dialog components access to common methods (validation, loading state, enable/disable confirm) without coupling them to store internals. A wrapper component is another option that would enforce consistency more strictly. This is an open question for team discussion.
* **File organization**: Dialog components are currently scattered across `pages/*/dialogs/` directories. They should move to `components/dialogs/` mirroring the page tree structure (e.g. `pages/team/dialogs/InviteMemberDialog.vue` becomes `components/dialogs/team/InviteMemberDialog.vue`).
* **Documentation**: The chosen pattern should be documented so new dialogs follow the same approach from the start.
## Priority targets
The iterated dialog cases (BlueprintTile, PipelineStage) are the most impactful and might be worth tackling first. After that, pages with multiple dialogs mounted simultaneously would be the next best wins.
## Approach
1. Formalize the dialog component contract, file organization, and migration pattern (team discussion)
2. Fix the two iterated cases (BlueprintTile, PipelineStage) as highest impact wins
3. Migrate simple confirmation dialogs across the app (~8-10 nearly identical dialogs)
4. Migrate remaining form/complex dialogs grouped by section
Subtasks will be scoped and created as we go.
Contributor guide
Assessment
This issue has not been assessed yet.