- modal.tsx Modal now accepts a showClose prop that passes through to
DialogContent, so callers that render their own close affordance
(DeployFeedbackModal) can suppress the Radix close button
- DeployFeedbackModal -> Modal with showClose=false. Custom header,
scroll body, embedded terminal, and footer stay in place; Modal
provides the consistent overlay/blur chrome. DialogTitle is still
imported from the dialog primitive purely as an sr-only
accessibility wrapper, since this status panel doesn't fit the §10
ModalHeader chrome
- BashExecModal -> Modal + ModalHeader. Kicker BASH · {CONTAINER}.
The xterm container sits in the body
- LogViewer -> Modal + ModalHeader. Kicker LOGS · {CONTAINER}. The
scroll region sits in the body
* refactor(frontend): migrate NodeManager dialogs to Modal chrome
Bring all four NodeManager dialogs onto §10 Modal primitives:
- Add node form -> Modal at lg, kicker NODES · ADD LOCAL or
NODES · ADD REMOTE depending on the form's type radio. The Add
button is now a normal button that calls setCreateOpen(true)
rather than a DialogTrigger
- Edit node form -> Modal at lg, kicker NODES · EDIT
- Pilot enrollment dialog -> Modal at xl, kicker NODES · PILOT ENROLLMENT
- Delete confirm -> destructive ConfirmModal, kicker
NODES · DELETE · IRREVERSIBLE
handleDelete now closes from finally so the dialog clears on errors
too, matching the ConfirmModal Promise-aware contract.
* fix(frontend): make ModalBody scrollable, scope nodes test locator to dialog
Two related fixes for the Add Node modal regression on small viewports:
1. ModalBody now caps at max-h-[calc(85vh-12rem)] with overflow-y-auto.
Tall forms that would push the footer offscreen on a 720px viewport
(Add Node has 6 form sections plus header and footer) now scroll
inside the body while the cyan-rail header and Cancel/Submit footer
stay anchored. Benefits every form modal, not just NodeManager.
2. e2e/nodes.spec.ts now scopes the submit-button locator to
getByRole('dialog'), removing the .last() pattern. The previous
approach worked when the modal was guaranteed to render after the
trigger in DOM order, but it doesn't survive an offscreen footer.
Replace raw Dialog/AlertDialog usage in ResourcesView with the §10 Modal
primitives:
- Prune confirms (managed and all scopes) -> destructive ConfirmModal
with single shared kicker and scope-conditional title/hint/label
- Delete confirms (image, network, volume) -> destructive ConfirmModal
- Bulk purge of unmanaged containers -> destructive ConfirmModal
- Create network form -> Modal + ModalHeader + ModalBody + ModalFooter
Also enforce single-line kickers in the modal primitive by adding
whitespace-nowrap to the header kicker, so the rune at the top never
wraps even at sm width.
Tighten handlePurgeOrphans to close the dialog in finally rather than
only on success, since the new ConfirmModal Promise-aware behaviour
keeps the dialog open until state closes it.
* refactor(frontend): extend Modal size system with wide variant
Adds 'wide' (max-w-5xl w-[95vw]) to ModalSize for dialogs that render
Monaco DiffEditor side-by-side, which need ~1024px to display both panels
clearly. The existing 'xl' cap at max-w-xl (576px) is too narrow for that
use case.
* refactor(frontend): migrate ComposeDiffPreviewDialog to Modal chrome (D-5)
Replaces raw Dialog/DialogHeader/DialogFooter with Modal size="wide",
ModalHeader, and ModalFooter. Kicker carries the stack name and file type
context; title is the file name (accessible dialog name for tests). Footer
hint reuses the existing "ON DISK → UNSAVED" label via the hint prop.
* refactor(frontend): migrate GitSourceDiffDialog to Modal chrome (D-5)
Replaces Dialog + nested AlertDialog with Modal size="wide", ModalHeader,
ModalFooter, and ConfirmModal. Kicker is "GIT · PULL PREVIEW"; title is
the stack name. Short SHA moves to the sr-only description. The "Deploy
after apply" checkbox in the footer hint slot uses normal-case and
tracking-normal on the Label to prevent KICKER_CLASS uppercase/tracking
from being inherited. The overwrite confirmation uses ConfirmModal with
variant="destructive" (rose rail) since it replaces local file content.
Replace raw Dialog/AlertDialog scaffolding in MfaEnrollDialog,
MfaDisableDialog, and MfaBackupCodesDialog with the shared Modal*
primitives introduced in D-1. Extract the duplicated BackupCodeTicket
component into a shared file consumed by both enroll and regen flows.
Fix a CSS grid auto-column blowout in Modal: the dialog's implicit auto
column was sizing to the step rail's min-content (492px), overflowing
the 448px max-w-md constraint and clipping right-side content. Adding
grid-cols-1 to the DialogContent override forces the column to use
minmax(0, 1fr), preventing any grid item from expanding the track past
available space. Also add min-w-0 to the TOTP secret code element so
its long monospaced string can truncate rather than drive column sizing.
Kicker prefixes updated to SECURITY · per the tracker convention.
Introduce ConfirmModal, an AlertDialog-rooted variant of the §10 modal
chrome for Yes-No confirmations. Reuses the cyan or destructive rail,
mono kicker, italic serif title, and footer hint via a parameterized
HeaderShell that injects Title and Description components so the same
helper renders Dialog or AlertDialog primitives correctly.
Replace the three inline AlertDialog blocks in EditorLayout (delete
stack, unsaved-load, label bulk action) with ConfirmModal. Hoist the
label bulk-action handler out of inline JSX and memoize the affected
stack list.
Async confirms (returning a Promise from onConfirm) keep the dialog
open so callers can render running state and close via onOpenChange;
sync confirms let Radix auto-close.
Introduces shared <Modal>, <ModalHeader>, <ModalBody>, <ModalFooter>,
and <ModalDestructiveHeader> primitives that wrap shadcn Dialog and
encapsulate the canonical modal chrome: cyan rail at the left edge,
mono uppercase kicker, italic serif title, footer hint, standardized
button order (secondary outline, primary cyan / destructive).
Migrates NewFolderDialog and DeleteFileConfirm onto the new primitives
as the first proof points. The destructive variant flips the rail and
kicker color to the destructive token without changing the chrome
recipe.