+
+
+ {scope && (
+
+ {scope}
+
+ )}
+
+
+ {children}
+ {clearable && (
+
+ )}
+
+ {help &&
{help}
}
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+}
+
+export default SettingsFieldRow;
diff --git a/packages/dashboard/app/components/settings/SettingsNumberRow.css b/packages/dashboard/app/components/settings/SettingsNumberRow.css
new file mode 100644
index 0000000000..bbb191470c
--- /dev/null
+++ b/packages/dashboard/app/components/settings/SettingsNumberRow.css
@@ -0,0 +1,5 @@
+/* SettingsNumberRow (U8 / KTD-10) — numeric input control slot. */
+
+.settings-number {
+ width: 100%;
+}
diff --git a/packages/dashboard/app/components/settings/SettingsNumberRow.tsx b/packages/dashboard/app/components/settings/SettingsNumberRow.tsx
new file mode 100644
index 0000000000..e31726bb6b
--- /dev/null
+++ b/packages/dashboard/app/components/settings/SettingsNumberRow.tsx
@@ -0,0 +1,63 @@
+/**
+ * SettingsNumberRow — numeric control composing SettingsFieldRow (U8 / KTD-10).
+ * Emits numbers (never strings) through onChange. An empty input emits null —
+ * the modal's null-as-delete signal — which is also what the clear affordance
+ * emits when `clearable` is set.
+ */
+import { SettingsFieldRow } from "./SettingsFieldRow";
+import type { SettingsNumberDescriptor } from "./types";
+import "./SettingsNumberRow.css";
+
+export interface SettingsNumberRowProps {
+ descriptor: SettingsNumberDescriptor;
+ value: number | null;
+ onChange: (value: number | null) => void;
+ error?: string;
+ /** Renders a reset-to-default affordance that emits onChange(null). */
+ clearable?: boolean;
+}
+
+export function SettingsNumberRow({
+ descriptor,
+ value,
+ onChange,
+ error,
+ clearable,
+}: SettingsNumberRowProps) {
+ const { key, label, help, scope, disabled, min, max, step, placeholder } = descriptor;
+ return (
+