diff --git a/pd/nw/dialog_abstractions.html b/pd/nw/dialog_abstractions.html
new file mode 100644
index 0000000000000000000000000000000000000000..0525ed1b665640ff50e3524426fe6d026408bacf
--- /dev/null
+++ b/pd/nw/dialog_abstractions.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link id="page_style" rel="stylesheet" type="text/css" href="css/default.css">
+  </head>
+  <body class="dialog_body">
+    <div class="container">
+      <form>
+        <fieldset style="width: 90%; border: 2px solid grey" disabled>
+          <legend data-i18n="abstractions.filebased"></legend>
+          <table style="width: 90%; table-layout: fixed;">
+          </table>
+        </fieldset>
+        <hr>
+        <fieldset style="width: 90%; border: 2px solid grey">
+          <legend data-i18n="abstractions.private"></legend>
+            <strong data-i18n="abstractions.global"></strong>
+            <hr>
+            <table style="width: 90%; table-layout: fixed;">
+            </table>
+            <br>
+            <strong data-i18n="abstractions.local"></strong>
+            <hr>
+            <table style="width: 90%; table-layout: fixed;">
+            </table>
+            <br>
+            <button id="selectall_button" type="button" onClick="selectall()" data-i18n="[title]abstractions.selectall_tt" style="float: right;">
+              <span data-i18n="abstractions.selectall"></span>
+            </button>
+        </fieldset>
+        <div class="submit_buttons">
+          <button id="delete_button" type="button" onClick="deleteselected()" data-i18n="[title]abstractions.delete_tt">
+            <span data-i18n="abstractions.delete"></span>
+          </button>
+        </div>
+        <hr>
+        <div class="submit_buttons">
+          <button type="button" onClick="cancel()" data-i18n="[title]abstractions.close_tt">
+            <span data-i18n="abstractions.close"></span>
+          </button>
+        </div>
+      </form>
+    </div>
+  <script>
+"use strict";
+var gui = require("nw.gui");
+var pdgui = require("./pdgui.js");
+
+// For translations
+var l = pdgui.get_local_string;
+
+pdgui.skin.apply(window);
+
+var pd_object_callback;
+var canvas;
+var deletequeue;
+
+function selectall() {
+    var checkboxes = document.querySelectorAll('input[type="checkbox"]:not(:checked)');
+    for(var i = 0, n = checkboxes.length; i < n; i++) {
+      if(!checkboxes[i].checked)
+      {
+        checkboxes[i].click();
+      }
+    }
+}
+
+function deleteselected() {
+    pdgui.pdsend(pd_object_callback, "delabstractions", Array.from(deletequeue).join(" "));
+    cancel();
+}
+
+function cancel() {
+    pdgui.pdsend(pd_object_callback, "cancel");
+}
+
+function populate_form(attrs) {
+    var filebased_abs = attrs.filebased_abs,
+        private_abs = attrs.private_abs,
+        i,
+        zero = 0;
+    canvas = attrs.canvas;
+    deletequeue = new Set();
+    var tables = document.querySelectorAll("table"),
+        filebased_table = tables[0],
+        privateglobal_table = tables[1],
+        privatelocal_table = tables[2];
+
+    for(i = 0; i < filebased_abs.length; i += 2)
+    {
+      var row = document.createElement("tr");
+          cell1 = document.createElement("td"),
+          cell2 = document.createElement("td"),
+          cell3 = document.createElement("td");
+      
+      cell1.textContent = filebased_abs[i];
+      cell1.style.setProperty("width", "70%");
+      cell2.textContent = filebased_abs[i+1];
+      cell2.style.setProperty("width", "20%");
+      row.appendChild(cell1);
+      row.appendChild(cell2);
+      row.appendChild(cell3);
+      filebased_table.appendChild(row);
+    }
+
+    for(i = 0; i < private_abs.length; i += 2)
+    {
+      var row = document.createElement("tr"),
+          cell1 = document.createElement("td"),
+          cell2 = document.createElement("td"),
+          cell3 = document.createElement("td");
+      
+      cell1.textContent = private_abs[i];
+      cell1.style.setProperty("width", "70%");
+      cell2.textContent = private_abs[i+1];
+      cell2.style.setProperty("width", "20%");
+
+      if(private_abs[i+1] === 0)
+      {
+        let input_elem = document.createElement("input"),
+            j = i;
+        input_elem.type = "checkbox";
+        input_elem.onchange = function() {
+            if(input_elem.checked) {
+                deletequeue.add(private_abs[j]);
+            } else {
+                deletequeue.delete(private_abs[j]);
+            }
+            get_elem("delete_button").disabled = (deletequeue.size === 0);
+            console.log(deletequeue.size);
+        };
+        cell3.appendChild(input_elem);
+        zero++;
+      }
+
+      row.appendChild(cell1);
+      row.appendChild(cell2);
+      row.appendChild(cell3);
+
+      if(private_abs[i][0] === '@') {
+          privatelocal_table.appendChild(row);
+      } else {
+          privateglobal_table.appendChild(row);
+      }
+    }
+
+    get_elem("selectall_button").disabled = (zero === 0);
+    get_elem("delete_button").disabled = true;
+}
+
+// This gets called from the nw_create_window function in index.html
+// It provides us with our window id from the C side.  Once we have it
+// we can create the menu and register event callbacks
+function register_window_id(gfxstub, attrs) {
+    pd_object_callback = gfxstub;
+    add_events(gfxstub);
+    // not sure that we need this for properties windows
+    //pdgui.canvas_map(gfxstub);
+    translate_form();
+    populate_form(attrs);
+    // We don't turn on rendering of the "container" div until
+    // We've finished displaying all the spans and populating the
+    // labels and form elements.  That makes it more efficient and
+    // snappier, at least on older machines.
+    document.getElementsByClassName("container")[0].style.setProperty("display", "inline");
+
+    gui.Window.get().setResizable(false);
+}
+
+function get_elem(name) {
+    return document.getElementById(name);
+}
+
+// Stop-gap translator
+function translate_form() {
+    var elements = document.querySelectorAll("[data-i18n]"),
+        data,
+        i;
+    for (i = 0; i < elements.length; i++) {
+        data = elements[i].dataset.i18n;
+        if (data.slice(0, 7) === "[title]") {
+            elements[i].title = l(data.slice(7));
+        } else {
+            elements[i].textContent = l(data);
+        }
+    }
+}
+
+function add_events(name) {
+    gui.Window.get().on("close", function () {
+        cancel();
+    });
+    pdgui.dialog_bindings(name);
+}
+  </script>
+  </body>
+</html>
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
index 6a0e72bae8428c1382893e88af7c75b79a5f123d..0f902cff6b735aab4c69c06e34ab4851c5ff84fe 100644
--- a/pd/nw/locales/en/translation.json
+++ b/pd/nw/locales/en/translation.json
@@ -235,6 +235,8 @@
     "visible_ancestor_tt": "give focus to the closest ancestor of this window that is currently visible",
     "pdwin": "Pd Window",
     "pdwin_tt": "Give focus to the main Pd window",
+    "abstractions": "Abstractions",
+    "abstractions_tt": "Consult and handle abstractions",
 
     "media": "Media",
 
@@ -485,5 +487,17 @@
     "building_index": "Building index...",
     "no_results": "No results found.",
     "search_placeholder": "Search Pd Docs"
+  },
+  "abstractions": {
+    "filebased": "File-based abstractions",
+    "private": "Private abstractions",
+    "global": "Global scope",
+    "local": "Local scope",
+    "selectall": "Select all",
+    "selectall_tt": "Select all definitions with zero instances",
+    "delete": "Delete",
+    "delete_tt": "Delete all selected definitions",
+    "close": "Close",
+    "close_tt": "Close the dialog window"
   }
 }
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 0bbcf24e9f6fa8a71771c37dcb657283c6edf11f..0cac8ec5ba9f8216b685882ff354d76f690208bb 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -1403,6 +1403,41 @@ function nw_undo_menu(undo_text, redo_text) {
     }
 }
 
+function ab_callback(cid, name) {
+    return function() {
+        pdgui.pdsend(cid, "delab", name);
+    }
+}
+
+function nw_ab_menu(cid, definitions) {
+    while(canvas_menu.ab.this.items.length > 2)
+        canvas_menu.ab.this.removeAt(2);
+    if(definitions.length === 0)
+    {
+        var item = new nw.MenuItem({
+            label: "Ø",
+            enabled: false
+        });
+        canvas_menu.ab.this.append(item);
+        canvas_menu.ab.clean.enabled = 0;
+    }
+    else
+    {
+        var i, del = 0;
+        for(i = 0; i < definitions.length; i += 2)
+        {
+            var item = new nw.MenuItem({
+                label: definitions[i] + " (" + definitions[i+1] + ")",
+                click: ab_callback(cid, definitions[i]),
+                enabled: (definitions[i+1] === 0)
+            });
+            canvas_menu.ab.this.append(item);
+            if(definitions[i+1] === 0) del++;
+        }
+        canvas_menu.ab.clean.enabled = (del > 0);
+    }
+}
+
 function have_live_box() {
     var state = canvas_events.get_state();
     if (state === "text" || state === "floating_text") {
@@ -1910,6 +1945,12 @@ function nw_create_patch_window_menus(gui, w, name) {
             pdgui.raise_pd_window();
         }
     });
+    minit(m.win.abstractions, {
+        enabled: true,
+        click: function () {
+            pdgui.pdsend(name, "getabstractions");
+        }
+    });
 
     // Media menu
     minit(m.media.audio_on, {
diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js
index cfbc48f25c561eb3a760906db1ce9a31eb4e7700..add45392f076b5f696c0c515b721ab238a872b0c 100644
--- a/pd/nw/pd_menus.js
+++ b/pd/nw/pd_menus.js
@@ -533,6 +533,11 @@ function create_menu(gui, type) {
             key: shortcuts.menu.pdwin.key,
             modifiers: shortcuts.menu.pdwin.modifiers
         }));
+        winman_menu.append(new gui.MenuItem({ type: "separator" }));
+        winman_menu.append(m.win.abstractions = new gui.MenuItem({
+            label: l("menu.abstractions"),
+            tooltip: l("menu.abstractions_tt")
+        }));
     }
 
     // Media menu
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 951cede88a9f8744d919fdcd1711ca38dfc8edf5..fab5208c98b7a73eeb414489561f4895c07c015a 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -5601,6 +5601,13 @@ function gui_external_dialog(did, external_name, attr_array) {
         });
 }
 
+function gui_abstractions_dialog(cid, gfxstub, filebased_abs, private_abs) {
+    var attrs = { canvas: cid, filebased_abs: filebased_abs,
+                    private_abs: private_abs };
+    dialogwin[gfxstub] = create_window(gfxstub, "abstractions", 300, 600,
+        0, 0, attrs);
+}
+
 // Global settings
 
 function gui_pd_dsp(state) {
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index d4961a0a13585c7487d8047835fc42dd22f6c572..04c2e6c7cf4189826514c8e1e898039097f8359b 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -2543,6 +2543,61 @@ static void *ab_new(t_symbol *s, int argc, t_atom *argv)
     return (newest);
 }
 
+static void canvas_getabstractions(t_canvas *x)
+{
+    t_canvas *c = canvas_getrootfor_ab(x),
+             *r = canvas_getrootfor(x);
+    gfxstub_deleteforkey(x);
+    char *gfxstub = gfxstub_new2(&x->gl_pd, &x->gl_pd);
+    t_ab_definition *abdef;
+    gui_start_vmess("gui_abstractions_dialog", "xs", x, gfxstub);
+    gui_start_array();
+    gui_end_array();
+    gui_start_array();
+    for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next)
+    {
+        char *hash = strrchr(abdef->ad_name->s_name, '#');
+        if(!hash)
+        {
+            if(abdef->ad_name->s_name[0] != '@' || !r->gl_isab)
+            {
+                gui_s(abdef->ad_name->s_name);
+                gui_i(abdef->ad_numinstances);
+            }
+        }
+        else
+        {
+            *hash = '\0';
+            if(r->gl_isab &&
+                gensym(abdef->ad_name->s_name) == r->gl_absource->ad_name)
+            {
+                gui_s(hash+1);
+                gui_i(abdef->ad_numinstances);
+            }
+            *hash = '#';
+        }
+    }
+    gui_end_array();
+    gui_end_vmess();
+}
+
+static void canvas_delabstractions(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
+{
+    t_symbol *name;
+    int i;
+    for(i = 0; i < argc; i++)
+    {
+        name = atom_getsymbol(argv++);
+        if(name->s_name[0] == '@')
+            name = ab_extend_name(x, name);
+        if(!canvas_del_ab(x, name))
+            bug("canvas_delabstractions");
+    }
+    startpost("info: a total of [%d] ab definitions have been deleted\n      > ", argc);
+    postatom(argc, argv-argc);
+    endpost();
+}
+
 /* --------- */
 
 static t_class *abdefs_class;
@@ -2594,53 +2649,6 @@ static void abdefs_instances(t_abdefs *x, t_symbol *s)
         error("abdefs: couldn't find definition for '%s'", s->s_name);
 }
 
-static void abdefs_del(t_abdefs *x, t_symbol *s)
-{
-    t_ab_definition *abdef;
-    if((abdef = canvas_find_ab(x->x_canvas, s)) && !abdef->ad_numinstances)
-    {
-        canvas_del_ab(x->x_canvas, s);
-        post("abdefs: definition for '%s' has been deleted", s->s_name);
-    }
-    else if(abdef)
-        error("abdefs: couldn't delete '%s', it has at least one instance", s->s_name);
-    else
-        error("abdefs: couldn't find definition for '%s'", s->s_name);
-}
-
-static void abdefs_clean(t_abdefs *x)
-{
-    t_canvas *c = canvas_getrootfor_ab(x->x_canvas);
-    t_ab_definition *abdef, *abdefpre;
-    t_binbuf *buf = binbuf_new();
-    int tot = 0;
-    for(abdef = c->gl_abdefs, abdefpre = 0; abdef; )
-    {
-        if(!abdef->ad_numinstances)
-        {
-            binbuf_addv(buf, "s", abdef->ad_name);
-            tot++;
-            if(abdefpre) abdefpre->ad_next = abdef->ad_next;
-            else c->gl_abdefs = abdef->ad_next;
-            binbuf_free(abdef->ad_source);
-            freebytes(abdef->ad_dep, sizeof(t_ab_definition*)*abdef->ad_numdep);
-            freebytes(abdef->ad_deprefs, sizeof(int)*abdef->ad_numdep);
-            freebytes(abdef, sizeof(t_ab_definition));
-            if(abdefpre) abdef = abdefpre->ad_next;
-            else abdef = c->gl_abdefs;
-        }
-        else
-        {
-            abdefpre = abdef;
-            abdef = abdef->ad_next;
-        }
-    }
-    startpost("abdefs: a total of [%d] ab definitions have been deleted\n> ", tot);
-    postatom(binbuf_getnatom(buf), binbuf_getvec(buf));
-    endpost();
-    binbuf_free(buf);
-}
-
 static void abdefs_menuopen(t_abdefs *x)
 {
     char buf[MAXPDSTRING];
@@ -2650,18 +2658,13 @@ static void abdefs_menuopen(t_abdefs *x)
     gui_start_vmess("gui_external_dialog", "s", gfx_tag);
     gui_s("[ab] definitions");
     gui_start_array();
-    gui_s("name  |  #instances   [delete?]_hidden"); gui_i(0);
-    gui_s("----------------------------------------_hidden"); gui_i(0);
     if(!c->gl_abdefs)
     {
-        gui_s("*no definitions*_hidden"); gui_i(0);
+        gui_s("Ø_hidden"); gui_i(0);
     }
     for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next)
     {
-        if(abdef->ad_numinstances)
-            sprintf(buf, "%s  |  %d    _hidden", abdef->ad_name->s_name, abdef->ad_numinstances);
-        else
-            sprintf(buf, "%s  |  %d    _toggle", abdef->ad_name->s_name, 0);
+        sprintf(buf, "%s (%d)_hidden", abdef->ad_name->s_name, abdef->ad_numinstances);
         gui_s(buf); gui_i(0);
     }
     gui_end_array();
@@ -2670,47 +2673,6 @@ static void abdefs_menuopen(t_abdefs *x)
 
 static void abdefs_dialog(t_abdefs *x, t_symbol *s, int argc, t_atom *argv)
 {
-    argc -= 2;
-    argv += 2;
-
-    t_canvas *c = canvas_getrootfor_ab(x->x_canvas);
-    t_ab_definition *abdef, *abdefpre;
-    t_binbuf *buf = binbuf_new();
-    int tot = 0;
-    for(abdef = c->gl_abdefs, abdefpre = 0; abdef; )
-    {
-        if(atom_getfloat(argv))
-        {
-            if(abdef->ad_numinstances) bug("abdefs_dialog");
-
-            binbuf_addv(buf, "s", abdef->ad_name);
-            tot++;
-
-            if(abdefpre) abdefpre->ad_next = abdef->ad_next;
-            else c->gl_abdefs = abdef->ad_next;
-            binbuf_free(abdef->ad_source);
-            freebytes(abdef->ad_dep, sizeof(t_ab_definition*)*abdef->ad_numdep);
-            freebytes(abdef->ad_deprefs, sizeof(int)*abdef->ad_numdep);
-            freebytes(abdef, sizeof(t_ab_definition));
-            if(abdefpre) abdef = abdefpre->ad_next;
-            else abdef = c->gl_abdefs;
-        }
-        else
-        {
-            abdefpre = abdef;
-            abdef = abdef->ad_next;
-        }
-        argv++;
-    }
-
-    if(tot)
-    {
-        startpost("abdefs: a total of [%d] ab definitions have been deleted\n> ", tot);
-        postatom(binbuf_getnatom(buf), binbuf_getvec(buf));
-        endpost();
-    }
-    binbuf_free(buf);
-
     gfxstub_deleteforkey(x);
 }
 
@@ -3577,13 +3539,15 @@ void g_canvas_setup(void)
     class_addbang(abdefs_class, (t_method)abdefs_bang);
     class_addmethod(abdefs_class, (t_method)abdefs_get, gensym("get"), 0);
     class_addmethod(abdefs_class, (t_method)abdefs_instances, gensym("instances"), A_SYMBOL, 0);
-    class_addmethod(abdefs_class, (t_method)abdefs_del, gensym("del"), A_SYMBOL, 0);
-    class_addmethod(abdefs_class, (t_method)abdefs_clean, gensym("clean"), 0);
     class_addmethod(abdefs_class, (t_method)abdefs_menuopen, gensym("menu-open"), 0);
     class_addmethod(abdefs_class, (t_method)abdefs_dialog, gensym("dialog"), A_GIMME, 0);
 
     class_addmethod(canvas_class, (t_method)canvas_showdirty,
         gensym("showdirty"), 0);
+    class_addmethod(canvas_class, (t_method)canvas_getabstractions,
+        gensym("getabstractions"), 0);
+    class_addmethod(canvas_class, (t_method)canvas_delabstractions,
+        gensym("delabstractions"), A_GIMME, 0);
 /*---------------------------- declare ------------------- */
     declare_class = class_new(gensym("declare"), (t_newmethod)declare_new,
         (t_method)declare_free, sizeof(t_declare), CLASS_NOINLET, A_GIMME, 0);