+ {/* Floating header bar — visible on hover/selected */}
+
+ {/* Drag grip */}
+ {dragHandleProps && (
+
+ ⠿
+
+ )}
+
+ {/* Type badge */}
+
+ {nodeType}
+
+
+ {/* Delete */}
+ {onDelete && (
+
+ )}
+
+
+ {/* Hover glow when NOT selected */}
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/ui-next/src/widgets-editor/components/NodePropertiesForm.tsx b/packages/ui-next/src/widgets-editor/components/NodePropertiesForm.tsx
new file mode 100644
index 00000000..852af80e
--- /dev/null
+++ b/packages/ui-next/src/widgets-editor/components/NodePropertiesForm.tsx
@@ -0,0 +1,266 @@
+import { useEffect, useId, useState } from "react";
+import type { ConfigField, ConfigSchema } from "@/widgets/types";
+
+// ─── Shared primitive helpers ─────────────────────────────────────────────────
+
+const label: React.CSSProperties = {
+ display: "block",
+ fontSize: "0.6875rem",
+ fontWeight: 600,
+ textTransform: "uppercase",
+ letterSpacing: "0.04em",
+ color: "#94a3b8",
+ marginBottom: "0.25rem",
+};
+
+const input: React.CSSProperties = {
+ width: "100%",
+ padding: "0.3rem 0.5rem",
+ fontSize: "0.8125rem",
+ borderRadius: "0.25rem",
+ border: "1px solid #334155",
+ background: "#0f172a",
+ color: "#e2e8f0",
+ outline: "none",
+ boxSizing: "border-box",
+};
+
+const description: React.CSSProperties = {
+ fontSize: "0.6875rem",
+ color: "#64748b",
+ marginTop: "0.2rem",
+};
+
+/** Text input that commits on blur/Enter, not on every keystroke. */
+function CommitInput({
+ value,
+ onChange,
+ type = "text",
+ min,
+ max,
+ placeholder,
+}: {
+ value: string;
+ onChange: (v: string) => void;
+ type?: string;
+ min?: number;
+ max?: number;
+ placeholder?: string;
+}) {
+ const [local, setLocal] = useState(value);
+ useEffect(() => setLocal(value), [value]);
+ return (
+