refactor(emcn): make ChipModal footer/header props-driven and migrate all consumers#4905
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Intentional UX fixes: unsaved-changes flows show “Keep editing” instead of generic Cancel; closing via header/overlay runs the same reset as the dismiss button; destructive confirms can’t be dismissed mid-request. Curated prompts (separate thread in the diff): integration names are Reviewed by Cursor Bugbot for commit 3901145. Configure here. |
Greptile SummaryThis PR makes the EMCN modal system fully declarative by converting
Confidence Score: 5/5Safe to merge — all destructive confirm and form modals are correctly migrated, pending/disabled guards are preserved, and the single-dismiss-path invariant in ChipConfirmModal is sound. The refactoring is thorough and internally consistent. The in-flight cancel guard (cancelDisabled), the teardown-desync fix, and the 'Keep editing' label restoration all address issues raised in prior review rounds. Migration logic is faithful across all inspected consumers: pending state, disabled conditions, label splits, and variant choices match the pre-migration behavior. The one observation (redundant Cancel button alongside Done/Close in display-only modals) is a consequence of the structural-Cancel design decision documented in the PR and does not affect correctness. Display-only modals in create-api-key-modal.tsx, copilot.tsx, and base-tags-modal.tsx now render a structural Cancel button alongside a semantically identical Done/Close button. Important Files Changed
Class Diagram%%{init: {'theme': 'neutral'}}%%
classDiagram
class ChipModal {
+open: boolean
+onOpenChange: (open: boolean) => void
+size?: string
+srTitle?: string
}
class ChipModalHeader {
+onClose: () => void
+icon?: ComponentType
+closeAriaLabel?: string
}
class ChipModalFooterShell {
-leftSlot?: ReactNode
-children: ReactNode
}
class ChipModalFooter {
+onCancel: () => void
+cancelDisabled?: boolean
+primaryAction: ChipModalFooterAction
+secondaryAction?: ChipModalFooterAction
}
class ChipModalFooterAction {
+label: ReactNode
+onClick: () => void
+disabled?: boolean
+variant?: string
}
class ChipConfirmModal {
+open: boolean
+onOpenChange: (open: boolean) => void
+title: ReactNode
+description?: ReactNode
+confirm: ChipConfirmAction
+dismissLabel?: string
+size?: string
+children?: ReactNode
}
class ChipConfirmAction {
+label: string
+onClick: () => void
+pending?: boolean
+pendingLabel?: string
+disabled?: boolean
+variant?: string
}
ChipModal <|-- ChipConfirmModal : wraps
ChipModalFooterShell <|-- ChipModalFooter : uses
ChipModalFooterShell <|-- ChipConfirmModal : uses
ChipModalFooter --> ChipModalFooterAction : primaryAction / secondaryAction
ChipConfirmModal --> ChipConfirmAction : confirm
Reviews (4): Last reviewed commit: "fix(emcn): restore Enter-to-submit in fo..." | Re-trigger Greptile |
Add `cancelDisabled` to ChipModalFooter and thread the pre-migration in-flight guards back into all 45 footers that had them, so destructive flows can no longer be dismissed mid-mutation.
|
Addressed the Cancel-during-mutation regression in
Not changed (by design):
Verified: @greptile review |
…l-migration # Conflicts: # apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
…l-migration # Conflicts: # apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx
Confirmations have a different button grammar than form modals: a named
dismiss decision ('Keep editing') plus a usually-destructive confirm, with
a single dismiss path — not the structural, never-relabeled Cancel that
ChipModalFooter guarantees. Forcing confirmations through the form footer
produced both the ambiguous-'Cancel' copy loss and the header-X/footer-Cancel
state-reset drift.
ChipConfirmModal models that grammar directly and owns the safety rails every
hand-rolled confirm had to remember: header-X / dismiss button / Esc all route
through onOpenChange (teardown can't desync), and 'pending' disables the
dismiss while the action is in flight. Extract a shared ChipModalFooterShell so
the footer chrome stays a single source of truth across both components.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate ~30 destructive-confirm and unsaved-changes dialogs across the app from hand-composed ChipModal + ChipModalFooter to the declarative ChipConfirmModal. Net effect: - Unsaved-changes dialogs read 'Keep editing' again (was an ambiguous 'Cancel' after the footer migration) via dismissLabel. - Header-X and dismiss now share one teardown path, fixing the api-keys / copilot / scheduled-tasks / base-tags / tables cases where dismissing via the X left the targeted row selected. - In-flight 'pending' disables the dismiss button uniformly, so destructive confirmations can't be dismissed mid-mutation. - Confirm-modal widths harmonized to 'sm' (several were an oversized 'md'). Form/editor modals and the three-way access-control unsaved dialog keep ChipModalFooter (structural Cancel is correct there). Also restores the JSON-cell font size in row-modal and the in-flight Cancel guard in invite-modal flagged in review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two dialogs preserved a typographic '…' from their pre-migration copy; the codebase uses three-dot '...' everywhere else. Align for consistency. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
The props-driven footer renders its primary action as a Chip (type='button'), so multi-field <form> modals lost implicit Enter submission when the old type='submit' control was removed. Add a hidden, disabled-mirroring submit button — the existing codebase idiom (a2a.tsx, mcp.tsx) — to create-base, row, and help modals so Enter submits exactly as before and still respects each form's in-flight/validation guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3901145. Configure here.
Summary
Makes the EMCN modal system fully declarative across two button grammars, and migrates every consumer to it.
1. Props-driven
ChipModalFooter/ChipModalHeader(form modals)ChipModalFooteris props-driven:onCancel,cancelDisabled,primaryAction, and optionalsecondaryAction(each aChipModalFooterActionwithlabel/onClick/variant/disabled) replace hand-composed footer markup. The structural Cancel is owned by the component and is intentionally never relabel-able — that guarantee is the whole point.ChipModalHeader.onCloseis required and always renders the close button + inset divider (dropped the optionalonClose/showDividerbranches).cancelDisabledrestores the pre-migration in-flight guard so destructive flows can't be dismissed mid-mutation.2. New
ChipConfirmModalprimitive (confirmation dialogs)A confirmation has a different grammar than a form: a named dismiss decision ("Keep editing") plus a usually-destructive confirm, and one logical dismiss path. Forcing that through the form footer caused two issues a reviewer caught — an ambiguous "Cancel" in unsaved-changes dialogs, and header-X / footer-Cancel state-reset drift. So confirmations get their own primitive instead of weakening the form footer:
ChipConfirmModaltakestitle/description/confirm{label,onClick,variant,pending,pendingLabel,disabled}/dismissLabel/ optionalchildren.onOpenChange(teardown can't desync), andpendingdisables the dismiss while the action is in flight.ChipModalFooterShell, so both components stay a single source of truth.dismissLabelis honest API here (a confirmation's dismiss is a named decision) — unsaved-changes dialogs read "Keep editing" again, whileChipModalFooterkeeps its non-relabel-able Cancel.3. Consumer migration
ChipConfirmModal.ChipModalFooter.Intentional behavior changes (consistent, on purpose)
pendingdisables the dismiss button uniformly).sm(several were an oversized defaultmd).Type of Change
Testing
tsc --noEmit,bun run lint:check, andbun run check:api-validationall clean. Every migrated dialog was diffed against its pre-migration version to confirm loading-label splits, disable gates, copy/emphasis, reset-on-dismiss, and variants are preserved exactly. Representative modals (create/edit/delete confirmations, unsaved-changes, invite, deploy, API key, knowledge base, transfer-ownership) exercised manually.Checklist