diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html
index 32814bd96fdd05fb99c0033111189b8e55858b97..e239da5b2090983fd89d28cb18d60c2bd2c567f6 100644
--- a/pd/nw/pd_canvas.html
+++ b/pd/nw/pd_canvas.html
@@ -399,6 +399,21 @@ function set_edit_menu_modals(state) {
     }
 }
 
+function nw_undo_menu(undo_text, redo_text) {
+    if (undo_text === 'no') {
+        modals.undo.enabled = false;
+    } else {
+        modals.undo.enabled = true;
+        modals.undo.label = l('menu.undo') + " " + undo_text;
+    }
+    if (redo_text === 'no') {
+        modals.redo.enabled = false;
+    } else {
+        modals.redo.enabled = true;
+        modals.redo.label = l('menu.redo') + " " + redo_text;
+    }
+}
+
 // Menus for the Patch window
 function nw_create_patch_window_menus (name) {
 
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 5f8fa708d4937989d9c0eecc20ba885bf64b3a1a..3d6e2a11675ebe7ad2039752f03b8f1330279cb0 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -1764,14 +1764,14 @@ function init_socket_events () {
             var prefix = arr[i].substring(0, 2);
             if (prefix == 'nw' || prefix == 'nn') {
                 nextCmd = arr[i].substring(3);
-                console.log("nextCmd is " + nextCmd);
+                //console.log("nextCmd is " + nextCmd);
                 cmdHeader = 1;
             } else if (cmdHeader) {
 	        nextCmd += arr[i];
-                console.log("2nd part of cmd is " + arr[i]);
+                //console.log("2nd part of cmd is " + arr[i]);
             } else {
                 // Show the remaining old tcl/tk messages in blue
-                //gui_post(arr[i], "blue");
+                gui_post(arr[i], "blue");
             }
             // check if we end with a semicolon followed by a newline
             if (nextCmd.slice(-1) === ";" && nextCmd.slice(-2) !== '\\') {
@@ -1779,10 +1779,10 @@ function init_socket_events () {
                 //nextCmd = nextCmd.replace(/'/g, "\\\'");
                 var selector = nextCmd.slice(0, nextCmd.indexOf(" "));
                 var args = nextCmd.slice(selector.length + 1, -1);
-                //console.log('About to eval: ' + selector + '(' + args + ');');
-                 eval(selector + '(' + args + ');');
-                 nextCmd = '';
-                 cmdHeader = 0;
+                console.log('About to eval: ' + selector + '(' + args + ');');
+                eval(selector + '(' + args + ');');
+                nextCmd = '';
+                cmdHeader = 0;
             }
 	}
 	// client.destroy();
@@ -3516,3 +3516,11 @@ function gui_textarea(cid, tag, x, y, max_char_width, text, font_size, state) {
         patchwin[cid].window.canvas_events.normal();
     }
 }
+
+function gui_undo_menu(cid, undo_text, redo_text) {
+    // we have to check if the window exists, because Pd starts
+    // up with two unvis'd patch windows used for garrays
+    if (cid !== 'nobody' && patchwin[cid] !== undefined) {
+        patchwin[cid].window.nw_undo_menu(undo_text, redo_text);
+    }
+}
diff --git a/pd/nw/todo.txt b/pd/nw/todo.txt
index 8486297f523c99d691059e5fab79a61321c26675..42274a4cdc2607b2cce20dc5cf42615f83761bd2 100644
--- a/pd/nw/todo.txt
+++ b/pd/nw/todo.txt
@@ -183,6 +183,8 @@ Everything else: (A [x] means we've fixed it)
     But we're doing an end-run around that entire edifice so we need to
     force it, probably inside text_setto
 [ ] abstract out multi-line text loop, use for text_new and text_set
+[ ] think about translating the undo/redo actions as they appear in the menu.
+    (Might be tricky to do)
 
 Crashers
 --------
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index c56ce0bc5608bf506e68ee77288d7267ac3e5898..bf1e4a527680527d99590b712b8d13c98be81489 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -760,10 +760,19 @@ void canvas_setundo(t_canvas *x, t_undofn undofn, void *buf,
     canvas_undo_name = name;
     //if (x && glist_isvisible(x) && glist_istoplevel(x))
     if (x)
+    {
         // enable undo in menu
-        sys_vgui("pdtk_undomenu .x%lx %s no\n", x, name);
+        //sys_vgui("pdtk_undomenu .x%lx %s no\n", x, name);
+        gui_vmess("gui_undo_menu", "xss",
+            x, name, "no");
+    }
     else if (hadone)
-        sys_vgui("pdtk_undomenu nobody no no\n");
+    {
+        /* can't figure out what this does... */
+        //sys_vgui("pdtk_undomenu nobody no no\n");
+        gui_vmess("gui_undo_menu ", "xss",
+            "nobody", "no", "no");
+    }
 }
 
     /* clear undo if it happens to be for the canvas x.
diff --git a/pd/src/g_undo.c b/pd/src/g_undo.c
index b0fb5b7126ecfd043625012ea5b2327620915c45..d5cfe419215f3f52ddbbee1991106e09ee78fb8e 100644
--- a/pd/src/g_undo.c
+++ b/pd/src/g_undo.c
@@ -28,7 +28,8 @@ t_undo_action *canvas_undo_init(t_canvas *x)
         x->u_last = a;
         a->prev = NULL;
         a->name = "no";
-        sys_vgui("pdtk_undomenu .x%lx no no\n", (t_int)a->x);
+        //sys_vgui("pdtk_undomenu .x%lx no no\n", (t_int)a->x);
+        gui_vmess("gui_undo_menu", "xss", (t_int)a->x, "no", "no");
     }
     else
     {
@@ -54,7 +55,8 @@ t_undo_action *canvas_undo_add(t_canvas *x, int type, const char *name,
     a->data = (void *)data;
     a->name = (char *)name;
     canvas_undo_name = name;
-    sys_vgui("pdtk_undomenu .x%lx %s no\n", x, a->name);
+    //sys_vgui("pdtk_undomenu .x%lx %s no\n", x, a->name);
+    gui_vmess("gui_undo_menu", "xss", x, a->name, "no");
     return(a);
 }
 
@@ -94,7 +96,9 @@ void canvas_undo_undo(t_canvas *x)
         glob_preset_node_list_check_loc_and_update();
         if (glist_isvisible(x) && glist_istoplevel(x))
         {
-            sys_vgui("pdtk_undomenu .x%lx %s %s\n",
+            //sys_vgui("pdtk_undomenu .x%lx %s %s\n",
+            //    x, undo_action, redo_action);
+            gui_vmess("gui_undo_menu", "xss",
                 x, undo_action, redo_action);
             text_checkvalidwidth(x);
             canvas_getscroll(x);
@@ -140,7 +144,9 @@ void canvas_undo_redo(t_canvas *x)
         glob_preset_node_list_check_loc_and_update();
         if (glist_isvisible(x) && glist_istoplevel(x))
         {
-            sys_vgui("pdtk_undomenu .x%lx %s %s\n",
+            //sys_vgui("pdtk_undomenu .x%lx %s %s\n",
+            //    x, undo_action, redo_action);
+            gui_vmess("gui_undo_menu", "xss",
                 x, undo_action, redo_action);
             text_checkvalidwidth(x);
             canvas_getscroll(x);
diff --git a/pd/src/s_print.c b/pd/src/s_print.c
index 620711a5198d1459d398577af34c506f14d3f284..23abebb32dd87bf72ba38a885ae97b84f76e954c 100644
--- a/pd/src/s_print.c
+++ b/pd/src/s_print.c
@@ -62,9 +62,9 @@ static void doerror(const void *object, const char *s)
     else
     {
         char obuf[MAXPDSTRING];
-        sys_vgui("pdtk_posterror {%s} 1 {%s}\n",
-            strnpointerid(obuf, object, MAXPDSTRING),
-            strnescape(upbuf, s, MAXPDSTRING));
+        //sys_vgui("pdtk_posterror {%s} 1 {%s}\n",
+        //    strnpointerid(obuf, object, MAXPDSTRING),
+        //    strnescape(upbuf, s, MAXPDSTRING));
         gui_vmess("gui_post_error", "sis",
             strnpointerid(obuf, object, MAXPDSTRING),
             1,
@@ -93,7 +93,9 @@ static void dologpost(const void *object, const int level, const char *s)
         //sys_vgui("::pdwindow::logpost {%s} %d {%s}\n", 
                  //strnpointerid(obuf, object, MAXPDSTRING), 
                  //level, strnescape(upbuf, s, MAXPDSTRING));
-        sys_vgui("pdtk_post {%s}\n", 
+        //sys_vgui("pdtk_post {%s}\n", 
+        //         strnescape(upbuf, s, MAXPDSTRING));
+        gui_vmess("gui_post", "s",
                  strnescape(upbuf, s, MAXPDSTRING));
     }
 }