diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx
index 6f4c86e2..bd2840b6 100644
--- a/frontend/src/components/FinalMaskForm.tsx
+++ b/frontend/src/components/FinalMaskForm.tsx
@@ -156,7 +156,6 @@ function TcpMaskItem({
onRemove: () => void;
}) {
const path = [...base, 'tcp', index];
- const type = Form.useWatch([...path, 'type'], form) as string | undefined;
return (
@@ -176,47 +175,60 @@ function TcpMaskItem({
/>
- {type === 'fragment' && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
- {type === 'sudoku' && (
- <>
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
- {type === 'header-custom' && (
-
- )}
+
+ (prev as Record)[String(path[0])] !== (curr as Record)[String(path[0])]
+ }
+ >
+ {({ getFieldValue }) => {
+ const type = getFieldValue([...path, 'type']) as string | undefined;
+ if (type === 'fragment') {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+ if (type === 'sudoku') {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+ if (type === 'header-custom') {
+ return ;
+ }
+ return null;
+ }}
+
);
}
diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx
index 60f09c60..4765b2d1 100644
--- a/frontend/src/pages/inbounds/InboundFormModal.tsx
+++ b/frontend/src/pages/inbounds/InboundFormModal.tsx
@@ -93,33 +93,40 @@ const { Text } = Typography;
function AdvancedSliceEditor({
form,
path,
+ wrapKey,
minHeight,
maxHeight,
}: {
form: FormInstance;
path: NamePath;
+ // When set, the editor wraps the inner value with `{ [wrapKey]: ... }` so
+ // the JSON the user sees matches the wire shape's slice envelope (e.g.
+ // `{ "settings": { ... } }`). Edits unwrap the outer key before writing
+ // back to the form. Mirrors the legacy modal's wrappedConfigValue.
+ wrapKey?: string;
minHeight?: string;
maxHeight?: string;
}) {
- // The editor keeps a local text buffer so partial / invalid JSON typing
- // doesn't clobber the form. lastEmitRef tracks the serialized form value
- // at the moment we last accepted a write — if useWatch later fires with
- // a different value than that, the form was changed from elsewhere
- // (Stream tab toggle, sibling JSON tab edit), and we re-sync.
+ const serialize = (value: unknown): string => {
+ const inner = value ?? {};
+ return JSON.stringify(wrapKey ? { [wrapKey]: inner } : inner, null, 2);
+ };
+
const watched = Form.useWatch(path, form);
const lastEmitRef = useRef('');
const [text, setText] = useState(() => {
- const initial = JSON.stringify(form.getFieldValue(path) ?? {}, null, 2);
+ const initial = serialize(form.getFieldValue(path));
lastEmitRef.current = initial;
return initial;
});
useEffect(() => {
- const formStr = JSON.stringify(watched ?? {}, null, 2);
+ const formStr = serialize(watched);
if (formStr === lastEmitRef.current) return;
setText(formStr);
lastEmitRef.current = formStr;
- }, [watched]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [watched, wrapKey]);
return (
)[wrapKey] ?? {}
+ : parsed;
+ form.setFieldValue(path, toWrite);
+ lastEmitRef.current = JSON.stringify(wrapKey ? { [wrapKey]: toWrite } : toWrite, null, 2);
} catch {
// invalid JSON; keep buffer, don't push to form
}
@@ -2621,6 +2631,7 @@ export default function InboundFormModal({
@@ -2640,6 +2651,7 @@ export default function InboundFormModal({
@@ -2659,6 +2671,7 @@ export default function InboundFormModal({