Skip to content

[chore] Split the trigger drawers and management section - #5571

Open
ardaerzin wants to merge 4 commits into
release/v0.106.2from
fe-chore/trigger-drawers-split
Open

[chore] Split the trigger drawers and management section#5571
ardaerzin wants to merge 4 commits into
release/v0.106.2from
fe-chore/trigger-drawers-split

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

Three files in the agent triggers feature had grown past the point where anyone could scan them: TriggerSubscriptionDrawer.tsx at 2089 lines, TriggerScheduleDrawer.tsx at 1153, and TriggerManagementSection.tsx at 1040. Together they held 29 inline components.

Changes

Each file becomes a composition root plus co-located modules.

TriggerSubscriptionDrawer.tsx  2089 -> 69   + drawers/subscription/ (11 modules)
TriggerScheduleDrawer.tsx      1153 -> 53   + drawers/schedule/ (7 modules)
TriggerManagementSection.tsx   1040 -> 377  + triggerManagement/ (6 modules)

The area already had a good set of shared primitives (MasterDetailRail, useDraftMasterDetail, DrawerFooter, RunVersionField, EventSourcePicker, SectionRail, RailField, ActiveToggle) and all three files were already consuming them correctly. There were no duplicated re-implementations to collapse, so every extracted module keeps importing the same primitives. No new overlapping abstraction was added.

Two genuine cross-drawer duplicates moved to drawers/shared/: RequiredTitle and normalizeJson.

Three near-duplicates deliberately stayed per-drawer, because sharing them would cost more than it saves:

  • MAX_DRAFTS and SHOW_LIST_RAIL hold the same values but carry different documenting comments. Merging them would delete one.
  • applicationRevisionAdapter is a module-level singleton. Merging the two would change adapter identity from two instances to one, which is a behaviour change with no payoff.
  • extractBoundRevId exists in the subscription drawer, while the schedule drawer writes the same three-key fallback inline. Replacing the inline version would be a rewrite, not a move.

Public surface is unchanged. Both drawers keep their default export, and useAgentTriggers and AddTriggerDropdown are re-exported from TriggerManagementSection.tsx, so no importer changed.

Tests

  • pnpm --filter @agenta/entity-ui exec tsc --noEmit passes, as do @agenta/oss and @agenta/ee.
  • pnpm lint-fix across the repo makes no changes. Prettier clean.
  • Effect bodies, dependency arrays and lazy initializers moved verbatim, comments included. That includes the large commented-out blocks from [fix] Unblock editing schedule and subscription triggers #5552, which are load-bearing (they carry "restore with the call site below").
  • Rebuilt against current release/v0.106.1 rather than rebased. [fix] Unblock editing schedule and subscription triggers #5552 changed the binding logic inside the regions this refactor deletes, so a naive replay would have dropped that fix. Verified line by line: of 86 lines [fix] Unblock editing schedule and subscription triggers #5552 added, 84 are present verbatim. The two that differ are the same import closer, which correctly became "../shared/RunVersionField" one directory deeper. extractBoundWorkflowId, isRunVersionBound, extractDefaultBindId, the storedReferences/versionChosen derivation and hideEnvironment all land in the module that now owns them.

Not runtime verified. The gates above are static.

What to QA

  • From a playground, open Schedules. The master detail rail lists existing schedules and the form edits on the right.
  • Edit an existing schedule and an existing subscription, then save. Both still save, which is the [fix] Unblock editing schedule and subscription triggers #5552 fix this branch had to preserve.
  • Create a new subscription and bind a workflow version. The Deployed option stays hidden, and saving without a binding still shows "Bind a workflow".
  • Run in playground from the schedule footer, and Run from a subscription row.
  • Regression: open Triggers from project settings, where there is no playground. Both drawers stay single create and edit forms with no rail.
  • Regression: the smart header in the subscription drawer still switches when you browse sources. Its atoms moved into subscription/constants.ts and are read by two modules.

Two drawer roots and the management section become composition roots plus
co-located modules. The existing shared primitives (MasterDetailRail,
useDraftMasterDetail, DrawerFooter, RunVersionField, EventSourcePicker,
SectionRail/RailField, ActiveToggle) were already used correctly, so every
extracted module keeps importing them — no new overlapping abstraction.

  TriggerSubscriptionDrawer.tsx  2089 -> 69   + drawers/subscription/ (11)
  TriggerScheduleDrawer.tsx      1153 -> 53   + drawers/schedule/ (7)
  TriggerManagementSection.tsx   1040 -> 377  + triggerManagement/ (6)

Two genuine cross-drawer duplicates move to drawers/shared/: RequiredTitle
and normalizeJson. Three near-duplicates deliberately stay per-drawer:
MAX_DRAFTS/SHOW_LIST_RAIL (same values, different documenting comments),
applicationRevisionAdapter (a module singleton — merging would change
adapter identity), and extractBoundRevId (the schedule drawer writes the
same fallback inline; replacing it would be a rewrite, not a move).

Split against current content so #5552 (unblock editing schedule and
subscription triggers) survives: extractBoundWorkflowId/isRunVersionBound,
extractDefaultBindId, the storedReferences/versionChosen derivation and the
hidden Deployed option all land in the module that now owns them. Verified
line-by-line against the upstream diff.

Public surface unchanged: both drawers keep their default export, and
useAgentTriggers / AddTriggerDropdown are re-exported from
TriggerManagementSection.tsx.
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 30, 2026
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Jul 30, 2026 6:13pm

Request Review

@dosubot dosubot Bot added Frontend refactoring A code change that neither fixes a bug nor adds a feature labels Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d814bdf-2255-4db1-bbfe-8d3555222f4a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fe-chore/trigger-drawers-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-production-ccfb.up.railway.app/w
Project agenta-oss-pr-5571
Image tag pr-5571-a0a939b
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-07-31T10:27:00.349Z

@ardaerzin
ardaerzin changed the base branch from release/v0.106.1 to release/v0.106.2 July 30, 2026 18:12
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/AppTriggerProviderGroups.tsx (1)

108-114: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Reuse a connections map instead of a linear find per row.

providerGroups already builds connById; hoisting it to its own memo lets connectionLabel be O(1) and drops the duplicate map construction.

♻️ Suggested change
+    const connById = useMemo(() => new Map(connections.map((c) => [c.id, c] as const)), [
+        connections,
+    ])
     const connectionLabel = useCallback(
         (connectionId?: string) => {
-            const c = connections.find((conn) => conn.id === connectionId)
+            const c = connectionId ? connById.get(connectionId) : undefined
             return c ? c.name || c.slug || c.integration_key : undefined
         },
-        [connections],
+        [connById],
     )
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/useAgentTriggers.ts (1)

43-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use workflowMolecule.selectors.artifactName for the bound workflow label.

defaultBoundLabel is user-facing, but deriving it from revision.name conflicts with the codebase’s explicit artifact name selector and the “use workflow artifact names for workflow display labels” guideline. Use the workflow molecule’s artifactName selector here so the drawer binds to the normalized artifact name instead of a raw revision name.

Source: Coding guidelines

web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/PillEditor.tsx (1)

86-114: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Missing dependency array — this effect re-runs on every render.

insertApi.current only needs reassigning when insertApi, makePill, or commit change.

♻️ Proposed change
-    })
+    }, [insertApi, makePill, commit])
web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleDrawerContent.tsx (1)

34-96: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip the schedules list fetch when the drawer is not in playground mode.

useTriggerSchedules() is called unconditionally, but the settings path only renders a single ScheduleForm and never needs the full list or draft master-detail state. Add a caller-controlled enabled: !!playgroundEntityId option (or equivalent disabled atom) to useTriggerSchedules() so settings-mode create/edit drawer opens avoid fetching the full schedules list.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: be6b10ad-dd29-4c71-afbd-e6b9bd5769e1

📥 Commits

Reviewing files that changed from the base of the PR and between a0ef2cc and 0748386.

📒 Files selected for processing (29)
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/TriggerManagementSection.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/AddTriggerDropdown.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/AppTriggerProviderGroups.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/SubscriptionChildRow.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/SubscriptionRunPopover.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/TriggerRow.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/triggerManagement/useAgentTriggers.ts
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerScheduleDrawer.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/TriggerSubscriptionDrawer.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/MessageComposer.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/RunInPlaygroundButton.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleDrawerContent.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleForm.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/SchedulesList.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/WindowField.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/constants.ts
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/shared/RequiredTitle.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/shared/normalizeJson.ts
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/InputsMappingField.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/MappingSection.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/PillEditor.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/RunSubscriptionButton.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/SourceBrowsePage.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/SourceField.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/SubscriptionDrawerContent.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/SubscriptionForm.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/SubscriptionsList.tsx
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/constants.ts
  • web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/subscription/helpers.ts

Comment on lines +175 to +193
useEffect(() => {
if (!isEdit || !schedule) return
setName(schedule.name ?? "")
setCron(schedule.data?.schedule ?? DEFAULT_CRON)
setStartTime(schedule.data?.start_time ?? null)
setEndTime(schedule.data?.end_time ?? null)
setEnabled(isEntityActive(schedule))
const refs = schedule.data?.references
const envRef = refs?.environment
if (envRef) {
setBindMode("environment")
setEnvironmentSlug(envRef.slug ?? null)
setAppSlug(refs?.application?.slug ?? null)
} else {
setWorkflowRevId(extractBoundWorkflowId(refs))
// Label is resolved from the revision id below, not stored as the raw id.
}
setInputsText(JSON.stringify(schedule.data?.inputs_fields ?? {}, null, 2))
}, [isEdit, schedule])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate ScheduleForm and schedule hook references =="
fd -a 'ScheduleForm\.tsx$|Schedule\.tsx$|.*schedule.*\.tsx$' web/packages/agenta-entity-ui 2>/dev/null | sed 's#^\./##'

echo
echo "== Repository files containing useTriggerSchedule/trigger schedule =="
rg -n "useTriggerSchedule|TriggerSchedule|scheduleId|refetchOnWindowFocus|staleTime|queryOptions" web -S --glob '*.ts' --glob '*.tsx' | head -200

Repository: Agenta-AI/agenta

Length of output: 27597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ScheduleForm relevant sections =="
sed -n '1,120p' web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleForm.tsx
sed -n '130,210p' web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleForm.tsx
sed -n '210,430p' web/packages/agenta-entity-ui/src/gatewayTrigger/drawers/schedule/ScheduleForm.tsx

echo
echo "== Trigger schedule query files =="
rg -n "useTriggerSchedule|queryTriggerSchedule|fetchTriggerSchedule|refetch|staleTime|enabled|schedules|TriggerSchedule" web/packages/agenta-entities/src/gatewayTrigger web/packages/agenta-sdk -S --glob '*.ts' --glob '*.tsx'

echo
echo "== Candidate query hook files =="
fd -a '\.ts$' web/packages/agento-entities web/packages/agenta-sdk 2>/dev/null | rg 'gatewayTrigger|trigger|schedule|hooks' | sed -n '1,120p'
fd -a '\.ts$' web/packages/agenta-sdk/src web/packages/agenta-sdk 2>/dev/null | sed -n '1,160p'

Repository: Agenta-AI/agenta

Length of output: 35970


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Exact hook implementation =="
sed -n '1,110p' web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSchedule.ts

echo
echo "== Trigger schedule schemas and response fields =="
rg -n "triggerScheduleSchema|triggerScheduleResponseSchema|updated_at|updated|created_at|fetched|schedule_id|TriggerScheduleResponse" web/packages/agenta-entities/src/gatewayTrigger web/packages/agenta-api-client/src/generated -S

echo
echo "== Trigger schedule schema source =="
fd -a '\.ts$' web/packages/agenta-entities/src/gatewayTrigger | xargs rg -n "triggerScheduleSchema|triggerResponse|TriggerScheduleResponse" -S
sed -n '1,140p' web/packages/agenta-entities/src/gatewayTrigger/hooks/useTriggerSchedule.ts

Repository: Agenta-AI/agenta

Length of output: 50372


Guard the edit-mode prefill to run at most once per schedule id.

useTriggerSchedule disables window-focus refetch and uses a 30s stale time, but save/other mutations can still make this query stale and refetch in the background. Since this effect’s dependency array is [isEdit, schedule], any fresh schedule object will reset inputsText and the other form fields the user is currently editing.

🛡️ Proposed fix: prefill once per schedule id
+    const prefilledIdRef = useRef<string | undefined>(undefined)
     useEffect(() => {
         if (!isEdit || !schedule) return
+        if (prefilledIdRef.current === schedule.id) return
+        prefilledIdRef.current = schedule.id ?? undefined
         setName(schedule.name ?? "")
         setCron(schedule.data?.schedule ?? DEFAULT_CRON)
         ...
     }, [isEdit, schedule])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!isEdit || !schedule) return
setName(schedule.name ?? "")
setCron(schedule.data?.schedule ?? DEFAULT_CRON)
setStartTime(schedule.data?.start_time ?? null)
setEndTime(schedule.data?.end_time ?? null)
setEnabled(isEntityActive(schedule))
const refs = schedule.data?.references
const envRef = refs?.environment
if (envRef) {
setBindMode("environment")
setEnvironmentSlug(envRef.slug ?? null)
setAppSlug(refs?.application?.slug ?? null)
} else {
setWorkflowRevId(extractBoundWorkflowId(refs))
// Label is resolved from the revision id below, not stored as the raw id.
}
setInputsText(JSON.stringify(schedule.data?.inputs_fields ?? {}, null, 2))
}, [isEdit, schedule])
const prefilledIdRef = useRef<string | undefined>(undefined)
useEffect(() => {
if (!isEdit || !schedule) return
if (prefilledIdRef.current === schedule.id) return
prefilledIdRef.current = schedule.id ?? undefined
setName(schedule.name ?? "")
setCron(schedule.data?.schedule ?? DEFAULT_CRON)
setStartTime(schedule.data?.start_time ?? null)
setEndTime(schedule.data?.end_time ?? null)
setEnabled(isEntityActive(schedule))
const refs = schedule.data?.references
const envRef = refs?.environment
if (envRef) {
setBindMode("environment")
setEnvironmentSlug(envRef.slug ?? null)
setAppSlug(refs?.application?.slug ?? null)
} else {
setWorkflowRevId(extractBoundWorkflowId(refs))
// Label is resolved from the revision id below, not stored as the raw id.
}
setInputsText(JSON.stringify(schedule.data?.inputs_fields ?? {}, null, 2))
}, [isEdit, schedule])

Comment on lines +87 to +89
useEffect(() => {
onErrorChange(parseError)
}, [parseError, onErrorChange])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Duplicate error ownership: parent overwrites this field's message.

In the non-agent path, MappingSection also validates value and calls onErrorChange (MappingSection.tsx Lines 85-96). Child effects flush before the parent's, so the parent wins: the detailed parser message is replaced by "Invalid JSON", and "Mapping must be a JSON object or a selector string" is cleared to null (parent's JSON.parse succeeds for arrays/strings). Let one owner validate — e.g. skip the parent's JSON effect when InputsMappingField is rendered.

Comment on lines +132 to +143
return (
<div className="relative">
<div
ref={ref}
contentEditable
suppressContentEditableWarning
role="textbox"
aria-multiline="true"
onInput={commit}
onKeyDown={onKeyDown}
className="box-border max-h-[280px] min-h-[120px] w-full overflow-y-auto whitespace-pre-wrap break-words rounded-lg border border-solid border-[var(--ag-colorBorder)] bg-[var(--ag-colorBgContainer)] px-3 py-2 text-xs leading-relaxed outline-none focus:border-[var(--ag-colorPrimary)]"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Force plain-text paste into the contenteditable.

Without an onPaste handler, pasted rich HTML is inserted verbatim into the editor: serialize then produces junk (nested elements collapse to "\n" + …), and pasted markup such as <img onerror=...> executes in-page (self-XSS).

🛡️ Proposed fix
+    const onPaste = useCallback(
+        (e: React.ClipboardEvent) => {
+            e.preventDefault()
+            const text = e.clipboardData.getData("text/plain")
+            document.execCommand("insertText", false, text)
+            commit()
+        },
+        [commit],
+    )
+
     return (
         <div className="relative">
             <div
                 ref={ref}
                 contentEditable
                 suppressContentEditableWarning
                 role="textbox"
                 aria-multiline="true"
                 onInput={commit}
+                onPaste={onPaste}
                 onKeyDown={onKeyDown}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<div className="relative">
<div
ref={ref}
contentEditable
suppressContentEditableWarning
role="textbox"
aria-multiline="true"
onInput={commit}
onKeyDown={onKeyDown}
className="box-border max-h-[280px] min-h-[120px] w-full overflow-y-auto whitespace-pre-wrap break-words rounded-lg border border-solid border-[var(--ag-colorBorder)] bg-[var(--ag-colorBgContainer)] px-3 py-2 text-xs leading-relaxed outline-none focus:border-[var(--ag-colorPrimary)]"
/>
const onPaste = useCallback(
(e: React.ClipboardEvent) => {
e.preventDefault()
const text = e.clipboardData.getData("text/plain")
document.execCommand("insertText", false, text)
commit()
},
[commit],
)
return (
<div className="relative">
<div
ref={ref}
contentEditable
suppressContentEditableWarning
role="textbox"
aria-multiline="true"
onInput={commit}
onPaste={onPaste}
onKeyDown={onKeyDown}
className="box-border max-h-[280px] min-h-[120px] w-full overflow-y-auto whitespace-pre-wrap break-words rounded-lg border border-solid border-[var(--ag-colorBorder)] bg-[var(--ag-colorBgContainer)] px-3 py-2 text-xs leading-relaxed outline-none focus:border-[var(--ag-colorPrimary)]"
/>

Comment on lines +295 to +319
const isDirty = useMemo(
() =>
baselineSnapshot !==
JSON.stringify({
name,
connectionId: connectionId ?? null,
eventKey,
enabled,
bindMode,
environmentSlug: environmentSlug ?? null,
workflowRevId: workflowRevId ?? null,
inputs: normalizeJson(inputsText),
}),
[
baselineSnapshot,
name,
connectionId,
eventKey,
enabled,
bindMode,
environmentSlug,
workflowRevId,
inputsText,
],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Create may be unsaveable when the source is pre-seeded.

canSave={isDirty && !alreadySubscribed} and the create-mode baseline already includes state?.connectionId, state?.eventKey and the default binding. When the drawer opens with a preselected connection/event (add-menu flow), the snapshot matches the initial state, so isDirty is false and Create stays disabled until the user edits an unrelated field. Consider canSave={(!isEdit || isDirty) && !alreadySubscribed} (create mode already validates required fields in buildData).

🐛 Proposed fix
-                canSave={isDirty && !alreadySubscribed}
+                canSave={(!isEdit || isDirty) && !alreadySubscribed}

Also applies to: 761-761

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Four defects the split surfaced, all pre-existing:

- Create was unreachable when the source is pre-seeded. The catalog opens the
  subscription drawer with connection + event + default binding, which is exactly
  the create baseline, so `isDirty` stayed false and Create never enabled. Create
  now gates on completeness (source + version chosen); Save still gates on dirty.
  Same change in the schedule form, which had the identical defect.
- The mapping field had two error owners. `MappingSection`'s JSON effect flushes
  after `InputsMappingField`'s, so it replaced the parser's message with "Invalid
  JSON" and cleared "Mapping must be a JSON object or a selector string" outright
  (its `JSON.parse` succeeds for arrays). The parent now defers to the child on
  the non-agent path.
- Edit-mode prefill reset the form on refetch. Every trigger mutation invalidates
  the detail query by prefix, so a background refetch re-ran the prefill and
  overwrote edits in progress. Both forms now hydrate once per loaded id; the
  subscription's `trigger_config` prefill folds into that same hydration.
- The pill composer accepted rich paste. Pasted markup landed in the
  contenteditable verbatim, where `serialize` collapses nested elements to "\n".
  Paste is now plain text inserted at the caret; the Enter handler reuses it.

Plus: bind the default label to the workflow artifact name instead of
`revision.name`, hoist the settings/playground split to the drawer roots so
settings stops mounting the list query and master-detail state, and hoist the
connections map in the provider groups.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Frontend refactoring A code change that neither fixes a bug nor adds a feature size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant