Skip to content

refactor(emcn): make ChipModal footer/header props-driven and migrate all consumers#4905

Merged
waleedlatif1 merged 8 commits into
stagingfrom
refactor/chip-modal-migration
Jun 9, 2026
Merged

refactor(emcn): make ChipModal footer/header props-driven and migrate all consumers#4905
waleedlatif1 merged 8 commits into
stagingfrom
refactor/chip-modal-migration

Conversation

@emir-karabeg

@emir-karabeg emir-karabeg commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

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)

  • ChipModalFooter is props-driven: onCancel, cancelDisabled, primaryAction, and optional secondaryAction (each a ChipModalFooterAction with label/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.onClose is required and always renders the close button + inset divider (dropped the optional onClose/showDivider branches).
  • cancelDisabled restores the pre-migration in-flight guard so destructive flows can't be dismissed mid-mutation.

2. New ChipConfirmModal primitive (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:

  • ChipConfirmModal takes title / description / confirm{label,onClick,variant,pending,pendingLabel,disabled} / dismissLabel / optional children.
  • It owns the safety rails every hand-rolled confirm used to re-implement: the header-X, dismiss button, Esc, and overlay click all route through one onOpenChange (teardown can't desync), and pending disables the dismiss while the action is in flight.
  • The footer chrome is extracted into a shared internal ChipModalFooterShell, so both components stay a single source of truth.
  • dismissLabel is honest API here (a confirmation's dismiss is a named decision) — unsaved-changes dialogs read "Keep editing" again, while ChipModalFooter keeps its non-relabel-able Cancel.

3. Consumer migration

  • All ~70 form/edit modals migrated to the props-driven footer/header.
  • ~30 destructive-confirm and unsaved-changes dialogs migrated to ChipConfirmModal.
  • Form/editor modals and the genuine three-way access-control unsaved dialog (Keep / Discard / Save) correctly stay on ChipModalFooter.

Intentional behavior changes (consistent, on purpose)

  • Unsaved-changes dialogs say "Keep editing" (was an ambiguous "Cancel" mid-refactor).
  • Dismissing a confirmation via the header-X now runs the same teardown as the dismiss button (fixes the api-keys / copilot / scheduled-tasks / base-tags / tables cases where the X left a row selected).
  • A destructive confirm can no longer be dismissed mid-mutation (pending disables the dismiss button uniformly).
  • Confirm-modal widths harmonized to sm (several were an oversized default md).

Type of Change

  • Refactor (behavior-preserving except the four intentional, consistent changes listed above)

Testing

tsc --noEmit, bun run lint:check, and bun run check:api-validation all 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

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 9, 2026 1:40am

Request Review

@cursor

cursor Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Very wide UI surface area (70+ modals) with a few intentional dismiss/copy behavior changes; curated-prompt mention rules affect post-signup home input but avoid touching free-form user text.

Overview
Refactors the EMCN modal stack so form modals use a props-driven ChipModalFooter (onCancel, primaryAction, optional secondaryAction) and confirmation dialogs move to a new ChipConfirmModal with named dismiss copy, unified dismiss/teardown, and pending blocking dismiss during mutations. Dozens of workspace, settings, knowledge, deploy, and auth/landing modals are migrated off hand-built Chip footers.

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 @-mentioned only at curated handoff seams—storeCuratedPrompt / populatePrompt / server-side mentionifyPromptForNames on landing template cards—so free-form landing prose isn’t auto-chipped. Suggested-actions no longer mentionify at list build time; workspace integration CTAs use storeCuratedPrompt. Mic button gets Tooltip + aria-label. Removes the unused add-task keyboard command.

Reviewed by Cursor Bugbot for commit 3901145. Configure here.

Comment thread apps/sim/app/workspace/[workspaceId]/scheduled-tasks/scheduled-tasks.tsx Outdated
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes the EMCN modal system fully declarative by converting ChipModalFooter and ChipModalHeader to props-driven APIs, introducing a new ChipConfirmModal primitive for confirmation dialogs, and migrating all ~100 consumers across the codebase.

  • ChipModalFooter now takes onCancel, cancelDisabled, primaryAction, and optional secondaryAction — Cancel is structural and non-relabelable, enforcing consistent cancel semantics across every form modal. cancelDisabled restores the in-flight guard so destructive mutations can't be silently dismissed mid-run.
  • ChipConfirmModal introduces a dedicated primitive for "are you sure?" dialogs with a single dismiss path (header-X, dismiss button, Esc, and overlay all route through onOpenChange), preventing the teardown desync bugs noted in earlier reviews. pending disables the dismiss button uniformly while a mutation is in flight.
  • Consumer migration covers ~70 form/edit modals and ~30 destructive-confirm and unsaved-changes dialogs, with intentional behavior improvements: "Keep editing" restored for unsaved-changes dialogs, header-X teardown fixed, and confirm-modal widths harmonized to sm.

Confidence Score: 5/5

Safe 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

Filename Overview
apps/sim/components/emcn/components/chip-modal/chip-modal.tsx Core change: ChipModalFooter made fully declarative (cancelDisabled, primaryAction, secondaryAction), ChipModalHeader.onClose made required, and new ChipConfirmModal primitive introduced with shared ChipModalFooterShell. API design is clean and well-documented.
apps/sim/components/emcn/components/index.ts Exports ChipConfirmModal, ChipConfirmAction, ChipConfirmModalProps, ChipModalFooterAction, and ChipModalFooterProps — all new public API surfaces from the chip-modal refactor.
apps/sim/app/workspace/[workspaceId]/settings/components/api-keys/components/create-api-key-modal/create-api-key-modal.tsx Form modal correctly migrated to declarative ChipModalFooter. The 'New API Key' display-only dialog now renders a redundant Cancel button alongside Done (both close the modal).
apps/sim/app/workspace/[workspaceId]/settings/components/copilot/copilot.tsx Delete confirms correctly migrated to ChipConfirmModal. Same Cancel+Done redundancy exists in the 'New API Key' show-key dialog as in create-api-key-modal.
apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/base-tags-modal/base-tags-modal.tsx Delete Tag confirm correctly migrated to ChipConfirmModal with teardown fix. Both the base-tags modal and View Documents dialog now show Cancel+Close where only Close existed before.
apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/transfer-ownership-dialog/transfer-ownership-dialog.tsx Complex form-like content correctly placed as ChipConfirmModal children. pending/disabled logic preserved: isSubmitting removed from disabled since confirm.pending covers it, and disabled condition for unmet prerequisites retained.
apps/sim/ee/access-control/components/access-control.tsx Correctly retained on ChipModalFooter for the three-way Keep/Discard/Save dialog; destructive confirm for group deletion migrated to ChipConfirmModal. Header X now routes through onOpenChange teardown path.
apps/sim/app/workspace/[workspaceId]/settings/components/team-management/components/remove-member-dialog/remove-member-dialog.tsx Correctly migrated to ChipConfirmModal; onCancel is now called from all dismiss paths (X, overlay, Esc) — intentional improvement from the PR. Error display uses a local styled p tag instead of ChipModalError (minor style deviation).
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx Both delete-columns and delete-table confirms migrated correctly. handleConfirmDeleteColumns captures deletingColumns before clearing it, matching the previous inline behavior. Delete-table confirm passes pending/pendingLabel.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal.tsx Multi-step wizard footer (schema/code sections) correctly migrated to secondaryAction with Back/Delete variants. Unsaved-Changes confirm now uses ChipConfirmModal with dismissLabel='Keep editing'.

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
Loading

Reviews (4): Last reviewed commit: "fix(emcn): restore Enter-to-submit in fo..." | Re-trigger Greptile

Comment thread apps/sim/components/emcn/components/chip-modal/chip-modal.tsx
Comment thread apps/sim/components/emcn/components/chip-modal/chip-modal.tsx Outdated
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.
@emir-karabeg

Copy link
Copy Markdown
Collaborator Author

Addressed the Cancel-during-mutation regression in 6c7cbdb68:

  • P1 (Cancel cannot be disabled during in-flight mutations): Added cancelDisabled?: boolean to ChipModalFooterProps and threaded it into the Cancel <Chip disabled={cancelDisabled}>. Restored the pre-migration in-flight guard on all 45 footers that previously had one (across 42 files) — using each modal's exact original boolean (isDeleting, isSubmitting, upsertKey.isPending, updateMutation.isPending || isGenerating, etc.). Multi-modal files are mapped correctly (e.g. byok's save modal is guarded, its delete modal isn't; nested "Keep Editing" unsaved-changes dialogs are intentionally left unguarded).
  • The onCancel TSDoc now permits disabling Cancel while keeping it structural — relabel/remove remain forbidden, so the consolidation intent is preserved.

Not changed (by design):

  • "Keep Editing" → "Cancel" relabel: intentional — ChipModalFooter deliberately forbids relabeling Cancel for cross-modal consistency.
  • forwardRef removed from ChipModalFooter: it's a leaf with no ref consumer; .displayName on a function component is valid.

Verified: biome check clean on all 43 files; tsc --noEmit reports no new errors.

@greptile review

…l-migration

# Conflicts:
#	apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
waleedlatif1 and others added 3 commits June 8, 2026 17:31
…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>
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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>
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor cursor 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.

✅ 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.

@waleedlatif1 waleedlatif1 merged commit 4f00baf into staging Jun 9, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the refactor/chip-modal-migration branch June 9, 2026 01:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants