diff --git a/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx b/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx
index ce60ddb663..d0e18dae46 100644
--- a/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx
+++ b/web/packages/agenta-entity-ui/src/testcase/TestcaseDataEditor.tsx
@@ -52,8 +52,18 @@ const MESSAGES_VIEW_OPTIONS: FieldViewModeOption[] = [
{value: "yaml", label: "YAML"},
]
-const getTestcaseViewOptions = ({dataType}: {dataType: DataType}): FieldViewModeOption[] =>
- dataType === "messages" ? MESSAGES_VIEW_OPTIONS : TESTCASE_VIEW_OPTIONS
+// JSON/YAML only — for number and boolean fields
+const CODE_ONLY_VIEW_OPTIONS: FieldViewModeOption[] = [
+ {value: "json", label: "JSON"},
+ {value: "yaml", label: "YAML"},
+]
+
+const getTestcaseViewOptions = ({dataType}: {dataType: DataType}): FieldViewModeOption[] => {
+ if (dataType === "messages") return MESSAGES_VIEW_OPTIONS
+ if (dataType === "number" || dataType === "boolean") return CODE_ONLY_VIEW_OPTIONS
+ return TESTCASE_VIEW_OPTIONS // strings, null → Text/Markdown/JSON/YAML
+}
+
const renderTestcaseTypeChip = (value: unknown) =>
function FullPayloadCodeEditor({
diff --git a/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx b/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx
index ed7a6144f1..776355a966 100644
--- a/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx
+++ b/web/packages/agenta-entity-ui/src/testcase/TestcaseDrillInFieldRenderer.tsx
@@ -1,6 +1,8 @@
import {useCallback, useEffect, useMemo} from "react"
import {
+ detectDataType,
+ type DataType,
JsonEditorWithLocalState,
MessagesField,
type CoreFieldRendererProps,
@@ -31,6 +33,8 @@ function CodeEditor({
viewMode,
onChange,
readOnly,
+ lockedType,
+ onLockType,
}: {
editorId: string
value: unknown
@@ -38,13 +42,25 @@ function CodeEditor({
viewMode?: ViewMode
onChange: (value: unknown) => void
readOnly?: boolean
+ lockedType?: DataType
+ onLockType?: (type: DataType) => void
}) {
if (viewMode !== "yaml") {
return (
onChange(parseCodeEditorValue(nextValue, value))}
+ onValidChange={(nextRaw) => {
+ const parsed = parseCodeEditorValue(nextRaw, value)
+ onChange(parsed)
+ // Dynamic type casting: if the parsed type changed, update metadata
+ if (onLockType) {
+ const newType = detectDataType(parsed, "native")
+ if (newType !== lockedType) {
+ onLockType(newType)
+ }
+ }
+ }}
readOnly={readOnly}
/>
)
@@ -63,11 +79,16 @@ function CodeEditor({
id={editorId}
initialValue={displayValue}
value={displayValue}
- handleChange={
- readOnly
- ? undefined
- : (nextValue) => onChange(parseCodeEditorValue(nextValue, value, viewMode))
- }
+ handleChange={(nextValue) => {
+ const parsed = parseCodeEditorValue(nextValue, value, viewMode)
+ onChange(parsed)
+ if (onLockType) {
+ const newType = detectDataType(parsed, "native")
+ if (newType !== lockedType) {
+ onLockType(newType)
+ }
+ }
+ }}
editorType="border"
className="min-h-[60px] overflow-hidden"
disableDebounce
@@ -111,6 +132,7 @@ function TextEditor({
markdown,
onChange,
readOnly,
+ lockedType,
}: {
editorId: string
value: unknown
@@ -118,14 +140,30 @@ function TextEditor({
markdown?: boolean
onChange: (value: unknown) => void
readOnly?: boolean
+ lockedType?: DataType
}) {
// Auto-infer native types from typed text so number / boolean values
// stop getting stored as strings. Anything that doesn't look exactly
// like a clean number or boolean literal stays a string — see
// inferPrimitiveFromText for the precise rules.
const handleChange = useCallback(
- (next: string) => onChange(inferPrimitiveFromText(next)),
- [onChange],
+ (next: string) => {
+ const inferred = inferPrimitiveFromText(next)
+ if (lockedType) {
+ const inferredType =
+ typeof inferred === "boolean"
+ ? "boolean"
+ : typeof inferred === "number"
+ ? "number"
+ : "string"
+ // Reject if type doesn't match — do NOT call onChange
+ if (inferredType !== lockedType) {
+ return
+ }
+ }
+ onChange(inferred)
+ },
+ [onChange, lockedType],
)
return (
@@ -173,6 +211,8 @@ export function TestcaseDrillInFieldRenderer({
dataType,
isRawMode,
viewMode,
+ lockedType,
+ onLockType,
}: CoreFieldRendererProps) {
const displayValue = useMemo(() => toDisplayString(value, viewMode), [value, viewMode])
const editorId = `testcase-field-${fullPathKey}`
@@ -200,6 +240,8 @@ export function TestcaseDrillInFieldRenderer({
viewMode={viewMode}
onChange={onChange}
readOnly={!editable}
+ lockedType={lockedType}
+ onLockType={onLockType}
/>
)
}
@@ -250,6 +292,7 @@ export function TestcaseDrillInFieldRenderer({
markdown
onChange={onChange}
readOnly={!editable}
+ lockedType={lockedType}
/>
)
}
@@ -262,6 +305,7 @@ export function TestcaseDrillInFieldRenderer({
displayValue={displayValue}
onChange={onChange}
readOnly={!editable}
+ lockedType={lockedType}
/>
)
}
@@ -276,6 +320,8 @@ export function TestcaseDrillInFieldRenderer({
viewMode={viewMode}
onChange={onChange}
readOnly={!editable}
+ lockedType={lockedType}
+ onLockType={onLockType}
/>
)
}
@@ -287,6 +333,7 @@ export function TestcaseDrillInFieldRenderer({
displayValue={displayValue}
onChange={onChange}
readOnly={!editable}
+ lockedType={lockedType}
/>
)
}