Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e2218ff
feat(entities): add app template atoms and ephemeral app factory
ardaerzin Apr 28, 2026
5a41db1
feat(drawer): support app-create context and lifecycle cleanup
ardaerzin Apr 28, 2026
4a23c23
refactor(apps): replace template modal with create-app dropdown
ardaerzin Apr 28, 2026
94a3c5e
refactor(prompts): route new prompt through drawer app-create flow
ardaerzin Apr 28, 2026
854a5fe
test(apps): cover lazy app creation drawer flow
ardaerzin Apr 28, 2026
5da102c
Merge branch 'feature/eval-evaluations' into frontend-feat/workflow-c…
ardaerzin Apr 29, 2026
0a55ae8
refactor: remove custom workflow from create-app dropdown and add typ…
ardaerzin Apr 29, 2026
e240610
Merge branch 'main' into frontend-feat/workflow-create-unification
ardaerzin May 6, 2026
214be97
Merge branch 'main' into frontend-feat/workflow-create-unification
ardaerzin May 6, 2026
b4a2b19
Merge branch 'main' into frontend-feat/workflow-create-unification
bekossy May 6, 2026
6d8c4b8
fix(frontend): address review feedback on app-create flow
ardaerzin May 6, 2026
5255817
fix(frontend): remove URL-safe validation from app name inputs
ardaerzin May 6, 2026
164724b
fix(frontend): use isSlugInputValid for URL-safe field validation
ardaerzin May 6, 2026
7fc76ca
fix(frontend): enable trace context on /prompts route
ardaerzin May 6, 2026
e1581d9
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
ardaerzin May 6, 2026
1e8c411
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
bekossy May 7, 2026
8d34a15
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
bekossy May 7, 2026
5ae8827
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
bekossy May 7, 2026
4c263b2
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
junaway May 7, 2026
7c376d7
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
junaway May 7, 2026
4856228
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
bekossy May 7, 2026
4aef30c
fix trace drawer URL sync loop when opened programmatically
ardaerzin May 7, 2026
f1855bb
Merge branch 'release/v0.99.3' into frontend-feat/workflow-create-uni…
bekossy May 7, 2026
a5a5ecb
Merge branch 'release/v0.99.6' into frontend-feat/workflow-create-uni…
bekossy May 11, 2026
17b44d9
Merge branch 'release/v0.99.6' into frontend-feat/workflow-create-uni…
bekossy May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {useWatch} from "antd/lib/form/Form"

import {useVaultSecret} from "@/oss/hooks/useVaultSecret"
import {LlmProvider} from "@/oss/lib/helpers/llmProviders"
import {isAppNameInputValid} from "@/oss/lib/helpers/utils"
import {isSlugInputValid} from "@/oss/lib/helpers/utils"
import {PROVIDER_KINDS, PROVIDER_LABELS, SecretDTOProvider} from "@/oss/lib/Types"

import LabelInput from "../../../assets/LabelInput"
Expand Down Expand Up @@ -239,7 +239,7 @@ const ConfigureProviderDrawerContent = ({
return Promise.reject(
"Please enter name",
)
if (!isAppNameInputValid(value)) {
if (!isSlugInputValid(value)) {
return Promise.reject(
"Name must contain only letters, numbers, underscore, or dash without any spaces.",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {selectedAppIdAtom} from "@/oss/state/app"
import {CommitVariantChangesModalProps} from "./assets/types"

const EVALUATOR_CREATE_FIELDS: CommitCreateFieldsConfig = {nameLabel: "Evaluator name"}
const APP_CREATE_FIELDS: CommitCreateFieldsConfig = {nameLabel: "App name"}
const VARIANT_CREATE_FIELDS: CommitCreateFieldsConfig = {
modes: ["variant"],
nameLabel: "Variant name",
Expand All @@ -38,6 +39,7 @@ const CommitVariantChangesModal: React.FC<CommitVariantChangesModalProps> = ({
const runnableData = useAtomValue(workflowMolecule.selectors.data(variantId || ""))
const isEphemeral = useAtomValue(workflowMolecule.selectors.isEphemeral(variantId || ""))
const isEvaluator = useAtomValue(workflowMolecule.selectors.isEvaluator(variantId || ""))
const isApplication = useAtomValue(workflowMolecule.selectors.isApplication(variantId || ""))

const appId = useAtomValue(selectedAppIdAtom)
const commitRevision = useSetAtom(playgroundController.actions.commitRevision)
Expand Down Expand Up @@ -226,8 +228,23 @@ const CommitVariantChangesModal: React.FC<CommitVariantChangesModalProps> = ({
[isEvaluator],
)

// For ephemeral entities, render a simplified "Create" modal with editable name
// For ephemeral entities, render a simplified "Create" modal with editable name.
// Branch the labels on the entity's type flag — evaluator-create flows show
// "Evaluator name", app-create flows show "App name", everything else
// (variant-from-base) keeps the evaluator default for backward compat.
if (isEphemeral) {
const createFields = isEvaluator
? EVALUATOR_CREATE_FIELDS
: isApplication
? APP_CREATE_FIELDS
: EVALUATOR_CREATE_FIELDS
// The drawer wrapper (`useDrawerCreateCommitCallback`) toasts
// "App created successfully" / "Evaluator created successfully"
// on its `onNewRevision` hook. Letting the modal also toast
// would surface two identical notifications. For unrecognized
// ephemeral flows (no evaluator / no application flag) we keep
// the modal toast as a fallback.
const successMessage = isEvaluator || isApplication ? null : "Created successfully"
return (
<EntityCommitModal
open={open}
Expand All @@ -239,8 +256,8 @@ const CommitVariantChangesModal: React.FC<CommitVariantChangesModalProps> = ({
}}
onSubmit={handleSubmit}
actionLabel="Create"
createEntityFields={EVALUATOR_CREATE_FIELDS}
successMessage="Evaluator created successfully"
createEntityFields={createFields}
successMessage={successMessage}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Plus, Trash} from "@phosphor-icons/react"
import {Button, Form, FormListFieldData, Input, InputNumber, Select, Switch, Typography} from "antd"
import dynamic from "next/dynamic"

import {isAppNameInputValid} from "@/oss/lib/helpers/utils"
import {isSlugInputValid} from "@/oss/lib/helpers/utils"

import {EVALUATOR_OPTIONS, NUMERIC_METRIC_TYPES} from "../../../constants"

Expand Down Expand Up @@ -131,7 +131,7 @@ const CreateNewMetric = ({
validator(_, value) {
if (!value) {
return Promise.resolve()
} else if (!isAppNameInputValid(value)) {
} else if (!isSlugInputValid(value)) {
return Promise.reject(
"Slug must contain only letters, numbers, underscore, or dash without any spaces.",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import deepEqual from "fast-deep-equal"
import {useSetAtom} from "jotai"
import {useDebounceValue} from "usehooks-ts"

import {isAppNameInputValid} from "@/oss/lib/helpers/utils"
import {isSlugInputValid} from "@/oss/lib/helpers/utils"
import {recordWidgetEventAtom} from "@/oss/lib/onboarding"
import {EvaluatorPreviewDto} from "@/oss/services/evaluations/api/evaluatorTypes"

Expand Down Expand Up @@ -326,7 +326,7 @@ const CreateEvaluator = ({
validator(_, value) {
if (!value) {
return Promise.resolve()
} else if (!isAppNameInputValid(value)) {
} else if (!isSlugInputValid(value)) {
return Promise.reject(
"Slug must contain only letters, numbers, underscore, or dash without any spaces.",
)
Expand Down
167 changes: 157 additions & 10 deletions web/oss/src/components/WorkflowRevisionDrawerWrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
parseEvaluatorKeyFromUri,
evaluatorTemplatesMapAtom,
workflowMolecule,
discardLocalServerDataAtom,
} from "@agenta/entities/workflow"
import {EntityPicker} from "@agenta/entity-ui"
import {PlaygroundConfigSection} from "@agenta/entity-ui/drill-in"
Expand All @@ -36,7 +37,9 @@ import {
import {type PlaygroundUIProviders} from "@agenta/playground-ui"
import {
DrawerProvidersProvider,
isCreateContext,
workflowRevisionDrawerAtom,
workflowRevisionDrawerContextAtom,
closeWorkflowRevisionDrawerAtom,
workflowRevisionDrawerCallbackAtom,
workflowRevisionDrawerEntityIdAtom,
Expand All @@ -51,6 +54,7 @@ import {Rocket} from "@phosphor-icons/react"
import {Button, Typography, message} from "antd"
import {getDefaultStore, useAtom, useAtomValue, useSetAtom} from "jotai"
import dynamic from "next/dynamic"
import {useRouter} from "next/router"

import OSSdrillInUIProvider from "@/oss/components/DrillInView/OSSdrillInUIProvider"
import SimpleSharedEditor from "@/oss/components/EditorViews/SimpleSharedEditor"
Expand All @@ -62,12 +66,14 @@ import {
} from "@/oss/components/Evaluators/components/ConfigureEvaluator/atoms"
import EvaluatorPlaygroundHeader from "@/oss/components/Evaluators/components/ConfigureEvaluator/EvaluatorPlaygroundHeader"
import {clearEvaluatorWorkflowCache} from "@/oss/components/Evaluators/store/evaluatorsPaginatedStore"
import {invalidateAppManagementWorkflowQueries} from "@/oss/components/pages/app-management/store"
import CommitVariantChangesButton from "@/oss/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton"
import DeployVariantButton from "@/oss/components/Playground/Components/Modals/DeployVariantModal/assets/DeployVariantButton"
import PlaygroundTestcaseEditor from "@/oss/components/Playground/Components/PlaygroundTestcaseEditor"
import {OSSPlaygroundShell} from "@/oss/components/Playground/OSSPlaygroundShell"
import SharedGenerationResultUtils from "@/oss/components/SharedGenerationResultUtils"
import {usePlaygroundNavigation} from "@/oss/hooks/usePlaygroundNavigation"
import useURL from "@/oss/hooks/useURL"
import {useQueryParamState} from "@/oss/state/appState"

const PlaygroundMainView = dynamic(
Expand Down Expand Up @@ -397,10 +403,19 @@ const DrawerPlayground = memo(({entityId}: {entityId: string}) => {
})

// ================================================================
// COMMIT CALLBACK (evaluator create mode)
// COMMIT CALLBACK (evaluator + app create modes)
//
// Fires on commit-success inside the drawer for:
// - evaluator-create: closes drawer, callback receives new revision ID
// - evaluator-view: updates drawer entityId to the newly committed revision
// - app-create: closes drawer FIRST (sync atom resets), then callback
// receives {newAppId, newRevisionId} so the dropdown
// handler can router.push to /apps/<id>/playground.
// Order: close → navigate (avoids drawer flicker on
// destination page during Next.js async transition).
// ================================================================

const useEvaluatorCommitCallback = () => {
const useDrawerCreateCommitCallback = () => {
const {context} = useAtomValue(workflowRevisionDrawerAtom)
const drawerCallback = useAtomValue(workflowRevisionDrawerCallbackAtom)
const drawerCallbackRef = useRef(drawerCallback)
Expand All @@ -410,21 +425,65 @@ const useEvaluatorCommitCallback = () => {
const closeDrawerRef = useRef(closeDrawer)
closeDrawerRef.current = closeDrawer

const router = useRouter()
const routerRef = useRef(router)
routerRef.current = router

const {baseAppURL} = useURL()
const baseAppURLRef = useRef(baseAppURL)
baseAppURLRef.current = baseAppURL

const isEvaluator = context === "evaluator-create" || context === "evaluator-view"
const isEvaluatorCreate = context === "evaluator-create"
const isAppCreate = context === "app-create"

useEffect(() => {
if (!isEvaluator) return
if (!isEvaluator && !isAppCreate) return

const previousOnNewRevision = getWorkflowCommitCallbacks().onNewRevision

registerWorkflowCommitCallbacks({
onNewRevision: async (result, params) => {
clearEvaluatorWorkflowCache()
if (isEvaluator) {
clearEvaluatorWorkflowCache()
}
await previousOnNewRevision?.(result, params)

if (isEvaluatorCreate) {
drawerCallbackRef.current?.(result.newRevisionId)
drawerCallbackRef.current?.({
configId: result.newRevisionId,
newRevisionId: result.newRevisionId,
})
closeDrawerRef.current()
} else if (isAppCreate) {
const newWorkflow = result.workflow as
| {workflow_id?: string; id?: string}
| undefined
const newAppId = newWorkflow?.workflow_id ?? newWorkflow?.id ?? undefined
const newRevisionId = result.newRevisionId

// Refresh the apps-page paginated table + count caches so
// the new app shows up immediately on /apps when the user
// navigates back. The shared workflow-list invalidation
// (commit.ts:590) doesn't cover the app-management
// paginated store.
void invalidateAppManagementWorkflowQueries()

// Fire the user callback first so any analytics/hooks
// see the result. Then handle navigation + close inside
// the wrapper itself — owning the routing here keeps the
// contract simple for callers and avoids brittleness from
// a callback closure capturing stale router references.
drawerCallbackRef.current?.({
newAppId,
newRevisionId,
})

if (newAppId && newRevisionId) {
routerRef.current.push(
`${baseAppURLRef.current}/${newAppId}/playground?revisions=${newRevisionId}`,
)
}
closeDrawerRef.current()
Comment on lines +477 to 487

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Double router.push navigation on app-create commit: wrapper navigates AND caller's onWorkflowCreated callback navigates

In useDrawerCreateCommitCallback, the isAppCreate branch both invokes drawerCallbackRef.current?.({newAppId, newRevisionId}) (line 477-480) AND performs its own routerRef.current.push(...) (line 483-485). All callers that pass onWorkflowCreatedCreateAppDropdown (CreateAppDropdown/index.tsx:115-118), AppManagement (index.tsx:75), and PromptsPage (PromptsPage.tsx:388) — also call router.push() to the same playground URL inside their callback. This results in two router.push calls to the same URL on every successful app creation, which can cause duplicate browser history entries, double renders, or race conditions during the Next.js page transition.

Wrapper comment vs. actual behavior

The wrapper's comment on line 472-476 says "owning the routing here keeps the contract simple for callers", implying callers should NOT navigate. But every caller does navigate. Either remove the wrapper's router.push (and let the callback own it) or remove the router.push from all onWorkflowCreated callbacks.

Prompt for agents
The wrapper's onNewRevision handler for isAppCreate does two things that conflict: (1) it invokes drawerCallbackRef.current which the callers use to router.push, and (2) it also calls routerRef.current.push itself. This causes double navigation.

Option A (recommended based on the wrapper's comments about 'owning the routing'): Keep the wrapper's router.push and remove the router.push from all onWorkflowCreated callbacks in CreateAppDropdown/index.tsx (lines 114-119), AppManagement index.tsx (line 75), and PromptsPage.tsx (line 388). The callers' callbacks should only do analytics/hooks, not navigation.

Option B: Remove lines 482-486 from the wrapper (the routerRef.current.push block) and let each caller's onWorkflowCreated callback handle its own navigation. This is simpler but means the wrapper doesn't own routing.

Either way, pick one owner for the navigation and remove the duplicate.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

} else {
// In evaluator-view mode, the selection change callback
Expand All @@ -437,9 +496,11 @@ const useEvaluatorCommitCallback = () => {
}

message.success(
isEvaluatorCreate
? "Evaluator created successfully"
: "Evaluator committed successfully",
isAppCreate
? "App created successfully"
: isEvaluatorCreate
? "Evaluator created successfully"
: "Evaluator committed successfully",
)
Comment on lines 498 to 504

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Double success toast on app-create commit: both commit callback and EntityCommitModal show 'App created successfully'

When an ephemeral app is committed, the flow is: CommitVariantChangesModal.handleSubmitcreateFromEphemeralinvokeWorkflowCommitCallbacksuseDrawerCreateCommitCallback's onNewRevision. The onNewRevision handler shows message.success("App created successfully") (line 499-500). Then control returns to handleSubmit which returns {success: true}. EntityCommitModal then also calls message.success(successMessage) where successMessage is "App created successfully" (set at CommitVariantChangesModal/index.tsx:243-245, displayed at EntityCommitModal.tsx:408-409). The user sees two identical toast notifications.

Prompt for agents
There are two sources of the success toast for app-create:
1. useDrawerCreateCommitCallback's onNewRevision handler at WorkflowRevisionDrawerWrapper/index.tsx:498-504 calls message.success
2. EntityCommitModal at agenta-entity-ui/src/modals/commit/components/EntityCommitModal.tsx:408-409 calls message.success(successMessage) after handleSubmit returns successfully

Fix: either pass successMessage={null} in CommitVariantChangesModal for app-create ephemeral entities (so the modal doesn't toast), or remove the message.success from the wrapper's onNewRevision handler for isAppCreate. The simplest fix is probably to set successMessage to null for app-create in CommitVariantChangesModal/index.tsx around line 241-245, since the wrapper already handles the toast.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

},
})
Expand All @@ -449,19 +510,105 @@ const useEvaluatorCommitCallback = () => {
onNewRevision: previousOnNewRevision,
})
}
}, [isEvaluator, isEvaluatorCreate])
}, [isEvaluator, isEvaluatorCreate, isAppCreate])
}

// ================================================================
// MAIN WRAPPER
// ================================================================

// ================================================================
// CROSS-CONTEXT CLEANUP — release local-* on close (idle, no commit)
//
// Wires `closeWorkflowRevisionDrawerAtom` to also dispatch
// `discardLocalServerDataAtom` for any local-* entity. Applies to all
// drawer-create contexts (app-create, evaluator-create, trace-replay).
//
// Commit-in-flight gate: if a commit just succeeded, the close was
// triggered BY the commit handler (above), and the entity has already
// been promoted to a real ID. Releasing the local-* entry then is
// safe — the new real entity is in a different atom family.
//
// If a commit is in-flight at the moment of close (e.g., user clicks
// the X mid-commit), the existing close-handler runs synchronously
// before the commit settles. Acceptable for v1: the commit will still
// complete on the server and the user can find the new app in the
// list. The orphan local-* would be cleared by the close, but the
// commit's discardWorkflowDraftAtom call already clears the draft
// layer; the local server data is dead either way.
// ================================================================

const useDrawerCloseCleanup = () => {
const isOpen = useAtomValue(workflowRevisionDrawerOpenAtom)
const entityIdRef = useRef<string | null>(null)
const entityId = useAtomValue(workflowRevisionDrawerEntityIdAtom)

// `closeWorkflowRevisionDrawerAtom` resets `isOpen` and `entityId`
// atomically in one Jotai write. React batches both updates into the
// same render, so a naive `entityIdRef.current = entityId` during
// render would clobber the ref with `null` BEFORE the cleanup effect
// fires. Only update the ref while the drawer is open, so we keep
// the last truthy ID around to discard on close.
if (isOpen && entityId && entityIdRef.current !== entityId) {
entityIdRef.current = entityId
}

const discard = useSetAtom(discardLocalServerDataAtom)
const discardRef = useRef(discard)
discardRef.current = discard

const prevOpenRef = useRef(isOpen)
useEffect(() => {
if (prevOpenRef.current && !isOpen) {
// Drawer just closed — release the entity that was open.
// Read from the ref captured BEFORE the close (since close
// resets entityId to null in the same render).
const id = entityIdRef.current
if (id) discardRef.current(id)
Comment on lines +543 to +567

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 useDrawerCloseCleanup never discards local- entity data because entityIdRef is clobbered to null before effect fires*

When closeWorkflowRevisionDrawerAtom is dispatched (store.ts:165-174), it atomically sets both workflowRevisionDrawerOpenAtom to false and workflowRevisionDrawerEntityIdAtom to RESET (null) in the same Jotai write. React 18 batches these into a single re-render where both values are updated simultaneously. During the render phase, entityIdRef.current = entityId (line 545) sets the ref to null — the new value. Then in the commit phase, the useEffect fires and reads entityIdRef.current, which is already null, so the if (id) guard on line 558 fails and discard is never called. This means orphan local-* entities from app-create / evaluator-create flows are never released from the atom family when the user closes the drawer without committing — a state/memory leak.

Suggested change
const entityIdRef = useRef<string | null>(null)
const entityId = useAtomValue(workflowRevisionDrawerEntityIdAtom)
entityIdRef.current = entityId
const discard = useSetAtom(discardLocalServerDataAtom)
const discardRef = useRef(discard)
discardRef.current = discard
const prevOpenRef = useRef(isOpen)
useEffect(() => {
if (prevOpenRef.current && !isOpen) {
// Drawer just closed — release the entity that was open.
// Read from the ref captured BEFORE the close (since close
// resets entityId to null).
const id = entityIdRef.current
if (id) discardRef.current(id)
const entityIdRef = useRef<string | null>(null)
const entityId = useAtomValue(workflowRevisionDrawerEntityIdAtom)
// Only update the ref when entityId is truthy. When closeDrawer()
// resets both isOpen and entityId atomically, the ref retains the
// last real entity ID so the effect can still discard it.
if (entityId) entityIdRef.current = entityId
const discard = useSetAtom(discardLocalServerDataAtom)
const discardRef = useRef(discard)
discardRef.current = discard
const prevOpenRef = useRef(isOpen)
useEffect(() => {
if (prevOpenRef.current && !isOpen) {
// Drawer just closed — release the entity that was open.
const id = entityIdRef.current
if (id) {
discardRef.current(id)
entityIdRef.current = null
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

entityIdRef.current = null
}
prevOpenRef.current = isOpen
}, [isOpen])
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// ================================================================
// REFRESH WARNING — beforeunload guard for unsaved drawer edits
//
// Fires the standard browser "you have unsaved changes" warning when
// the user tries to refresh / close tab while a drawer-create context
// is open AND the entity has unsaved edits. Cross-context: covers
// app-create, evaluator-create, trace-replay.
// ================================================================

const useUnsavedDrawerWarning = () => {
const isOpen = useAtomValue(workflowRevisionDrawerOpenAtom)
const context = useAtomValue(workflowRevisionDrawerContextAtom)
const entityId = useAtomValue(workflowRevisionDrawerEntityIdAtom)
const isDirty = useAtomValue(
useMemo(() => workflowMolecule.atoms.isDirty(entityId ?? "__none__"), [entityId]),
)

useEffect(() => {
if (!isOpen || !isCreateContext(context) || !isDirty) return
const handler = (e: BeforeUnloadEvent) => {
e.preventDefault()
// Modern browsers ignore the message, but setting returnValue
// is required to trigger the prompt at all.
e.returnValue = ""
}
window.addEventListener("beforeunload", handler)
return () => window.removeEventListener("beforeunload", handler)
}, [isOpen, context, isDirty])
}

const WorkflowRevisionDrawerWrapper = () => {
const isOpen = useAtomValue(workflowRevisionDrawerOpenAtom)
const entityId = useAtomValue(workflowRevisionDrawerEntityIdAtom)
const [, setQueryRevision] = useQueryParamState("revisionId")

useEvaluatorCommitCallback()
useDrawerCreateCommitCallback()
useDrawerCloseCleanup()
useUnsavedDrawerWarning()

// Clear revisionId from URL when drawer closes
const prevOpenRef = useRef(isOpen)
Expand Down
Loading
Loading