diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json
index 69265bfb63f1c1b15d03fda5505a0f7f4b0606cb..798fc5cb9852ab30480f068447d0c7e3eb5868ab 100644
--- a/pd/nw/locales/de/translation.json
+++ b/pd/nw/locales/de/translation.json
@@ -297,6 +297,10 @@
       "none": "Keinen",
       "none_tt": "Ersetze keinen der Subpatches"
     },
+    "warning": {
+      "unsaved_tt": "Es gibt ein ungesichertes Exemplar dieser Abstraktion",
+      "multipleunsaved_tt": "Es gibt ein weiteres ungesichertes Exemplar dieser Abstraktion"
+    },
     "find": {
       "placeholder": "Suche im Patch",
       "search": "Suche",
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
index 5a161dce5dfecc453737810408be61c45e06f961..2a24c5494589e9f1628308087fe25ff875d08527 100644
--- a/pd/nw/locales/en/translation.json
+++ b/pd/nw/locales/en/translation.json
@@ -297,6 +297,10 @@
       "none": "None",
       "none_tt": "Do not replace any subpatch"
     },
+    "warning": {
+      "unsaved_tt": "There is an unsaved edited instance of this abstraction",
+      "multipleunsaved_tt": "There is another unsaved edited instance of this abstraction"
+    },
     "find": {
       "placeholder": "Search in Canvas",
       "search": "Search",
diff --git a/pd/nw/locales/fr/translation.json b/pd/nw/locales/fr/translation.json
index 8a34087870ad86fb7a26f84036bff88fba8ce153..6e476f33beca113d02f129c80f7236862c11a8fc 100644
--- a/pd/nw/locales/fr/translation.json
+++ b/pd/nw/locales/fr/translation.json
@@ -297,6 +297,10 @@
       "none": "Aucun",
       "none_tt": "Ne remplacer aucun sous-patch"
     },
+    "warning": {
+      "unsaved_tt": "Il existe une instance modifiée non enregistrée de cette abstraction",
+      "multipleunsaved_tt": "Il existe une autre instance modifiée non enregistrée de cette abstraction"
+    },
     "find": {
       "placeholder": "Chercher dans le Canevas",
       "search":    "Chercher",
diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html
index 7ba063a2740a95b6116765e9385176ee7b61b4ac..d04950e96f1ecf1e27559a98cadda8b79b3aaa51 100644
--- a/pd/nw/pd_canvas.html
+++ b/pd/nw/pd_canvas.html
@@ -102,6 +102,9 @@
         </button>
       </div>
     </dialog>
+    <div style="position: fixed; right: 2%; top: 2%; ">
+      <strong id="canvas_warning" style="display: none; text-align: right; -webkit-user-select: none; cursor: pointer;">!</strong>
+    </div>
 	<div id="hscroll" style="position: fixed; left: 0px; bottom: 0px; border-radius: 0px; width: 10px; height: 5px; visibility: hidden;"></div>
 	<div id="vscroll" style="position: fixed; right: 0px; top: 0px; border-radius: 0px; width: 5px; height: 10px; visibility: hidden;"></div>
     <script type="text/javascript" src="./pd_canvas.js"></script>
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 60278d79f958af3e413dea8b8bc2e5f525c029e6..a0bfb1a2e49a248c2d19ed3884943002f3f463dd 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -1336,6 +1336,9 @@ function register_window_id(cid, attr_array) {
     // we check the title_queue to see if our title now contains an asterisk
     // (which is the visual cue for "dirty")
 
+    // Enable/disable the warning for multiple dirty instances
+    pdgui.gui_canvas_warning(cid, attr_array.warid);
+
     // Two possibilities for handling this better:
     // have a representation of canvas attys in pdgui.js (editmode, dirty, etc.)
     // or
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index a23dc55d083141ea6d5e67f5c36ee8bf9f376450..5ef0918ddd06a96d9aaa4d5482f0fd74b7fc9064 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -1760,7 +1760,7 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) {
 }
 
 // create a new canvas
-function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, has_toplevel_scalars, cargs) {
+function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name, dir, dirty_flag, warid, hide_scroll, hide_menu, has_toplevel_scalars, cargs) {
     // hack for buggy tcl popups... should go away for node-webkit
     //reset_ctrl_on_popup_window
     
@@ -1817,6 +1817,7 @@ function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name
             name: name,
             dir: dir,
             dirty: dirty_flag,
+            warid: warid,
             args: cargs,
             zoom: zoom,
             editmode: editmode,
@@ -2860,6 +2861,35 @@ function gui_gobj_dirty(cid, tag, state) {
     });
 }
 
+function gui_canvas_warning(cid, warid) {
+    var warning = get_item(cid, "canvas_warning");
+    switch(warid)
+    {
+        case 0:
+            warning.style.setProperty("display", "none");
+            break;
+        case 1:
+            warning.title = lang.get_local_string("canvas.warning.unsaved_tt");
+            warning.onclick = function(){ pdsend(cid, "showdirty"); }
+            warning.style.setProperty("color", "coral");
+            warning.style.setProperty("font-size", "x-large");
+            warning.style.setProperty("display", "inline");
+            break;
+        case 2:
+            warning.title = lang.get_local_string("canvas.warning.multipleunsaved_tt");
+            warning.onclick = function(){ pdsend(cid, "showdirty"); }
+            warning.style.setProperty("color", "red");
+            warning.style.setProperty("font-size", "xx-large");
+            warning.style.setProperty("display", "inline");
+            break;
+
+        default:
+            break;
+    }
+}
+
+exports.gui_canvas_warning = gui_canvas_warning;
+
 function gui_canvas_emphasize(cid) {
     gui(cid).get_elem("patchsvg", function(e) {
         // raise the window
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index c88379b5cfee55671c85b168add2bbb881ae39c6..8cabb3506b34b0b5175ddeca4ed106f7df352003 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -396,6 +396,8 @@ static int calculate_zoom(t_float zoom_hack)
   return zoom;
 }
 
+int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess);
+
     /* make a new glist.  It will either be a "root" canvas or else
     it appears as a "text" object in another window (canvas_getcurrent() 
     tells us which.) */
@@ -472,6 +474,7 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     else x->gl_env = 0;
 
     x->gl_subdirties = 0;
+    x->gl_dirties = 0;
 
     if (yloc < GLIST_DEFCANVASYLOC)
         yloc = GLIST_DEFCANVASYLOC;
@@ -522,6 +525,14 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     canvas_field_vec = NULL;
     canvas_field_gp = NULL;
 
+    /* in the case it is an abstraction (gl_env is not null)
+        get the number of dirty instances of this same abstraction */
+    if(x->gl_env)
+    {
+        x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name,
+                                canvas_getdir(x), 0);
+    }
+
     return(x);
 }
 
@@ -792,6 +803,100 @@ void canvas_dirtyclimb(t_canvas *x, int n)
     }
 }
 
+/* the following functions are used to broadcast messages to all instances of
+    a specific abstraction (either file-based or ab).
+    the state of these instances change accoring to the message sent. */
+
+void clone_iterate(t_pd *z, t_canvas_iterator it, void* data);
+int clone_match(t_pd *z, t_symbol *name, t_symbol *dir);
+
+static void canvas_dirty_common(t_canvas *x, int mess)
+{
+    if(mess == 2)
+    {
+        if(x->gl_dirty)
+        {
+            if(!x->gl_havewindow) canvas_vis(x, 1);
+            gui_vmess("gui_canvas_emphasize", "x", x);
+        }
+    }
+    else
+    {
+        x->gl_dirties += mess;
+        if(x->gl_havewindow)
+            canvas_warning(x, (x->gl_dirties > 1 ?
+                                (x->gl_dirty ? 2 : 1)
+                                : (x->gl_dirties ? !x->gl_dirty : 0)));
+    }
+}
+
+/* packed data passing structure for canvas_dirty_broadcast */
+typedef struct _dirty_broadcast_data
+{
+    t_symbol *name;
+    t_symbol *dir;
+    int mess;
+    int *res;   /* return value */
+} t_dirty_broadcast_data;
+
+static void canvas_dirty_deliver_packed(t_canvas *x, t_dirty_broadcast_data *data)
+{
+    *data->res += (x->gl_dirty > 0);
+    canvas_dirty_common(x, data->mess);
+}
+
+static int canvas_dirty_broadcast_packed(t_canvas *x, t_dirty_broadcast_data *data);
+
+static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, int mess)
+{
+    int res = 0;
+    t_gobj *g;
+    for (g = x->gl_list; g; g = g->g_next)
+    {
+        if(pd_class(&g->g_pd) == canvas_class)
+        {
+            if(canvas_isabstraction((t_canvas *)g)
+                && ((t_canvas *)g)->gl_name == name
+                && canvas_getdir((t_canvas *)g) == dir)
+            {
+                res += (((t_canvas *)g)->gl_dirty > 0);
+                canvas_dirty_common((t_canvas *)g, mess);
+            }
+            else
+                res += canvas_dirty_broadcast((t_canvas *)g, name, dir, mess);
+        }
+        else if(pd_class(&g->g_pd) == clone_class)
+        {
+            int cres = 0;
+            t_dirty_broadcast_data data;
+            data.name = name; data.dir = dir; data.mess = mess; data.res = &cres;
+            if(clone_match(&g->g_pd, name, dir))
+            {
+                clone_iterate(&g->g_pd, canvas_dirty_deliver_packed, &data);
+            }
+            else
+            {
+                clone_iterate(&g->g_pd, canvas_dirty_broadcast_packed, &data);
+            }
+            res += cres;
+        }
+    }
+    return (res);
+}
+
+static int canvas_dirty_broadcast_packed(t_canvas *x, t_dirty_broadcast_data *data)
+{
+    *data->res = canvas_dirty_broadcast(x, data->name, data->dir, data->mess);
+}
+
+int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess)
+{
+    int res = 0;
+    t_canvas *x;
+    for (x = pd_this->pd_canvaslist; x; x = x->gl_next)
+        res += canvas_dirty_broadcast(x, name, dir, mess);
+    return (res);
+}
     /* mark a glist dirty or clean */
 void canvas_dirty(t_canvas *x, t_floatarg n)
 {
@@ -806,6 +911,15 @@ void canvas_dirty(t_canvas *x, t_floatarg n)
 
         /* set dirtiness visual markings */
         canvas_dirtyclimb(x2, (unsigned)n);
+        /* in the case it is an abstraction, we tell all other
+            instances that there is eiher one more dirty instance or
+            one less dirty instance */
+        if(canvas_isabstraction(x2)
+            && (x2->gl_owner || x2->gl_isclone))
+        {
+            canvas_dirty_broadcast_all(x2->gl_name, canvas_getdir(x2),
+                (x2->gl_dirty ? 1 : -1));
+        }
     }
 }
 
@@ -1034,6 +1148,15 @@ extern void canvas_group_free(t_pd *x);
 void canvas_free(t_canvas *x)
 {
     //fprintf(stderr,"canvas_free %zx\n", (t_uint)x);
+
+    /* in the case it is a dirty abstraction, we tell all other
+        instances that there is one less dirty instance */
+    if(canvas_isabstraction(x) && x->gl_dirty
+        && (x->gl_owner || x->gl_isclone))
+    {
+        canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), -1);
+    }
+
     t_gobj *y;
     int dspstate = canvas_suspend_dsp();
 
@@ -1893,6 +2016,13 @@ void canvas_redrawallfortemplatecanvas(t_canvas *x, int action)
     canvas_redrawallfortemplate(0, action);
 }
 
+/* --------- */
+
+static void canvas_showdirty(t_canvas *x)
+{
+    canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2);
+}
+
 /* ------------------------------- declare ------------------------ */
 
 /* put "declare" objects in a patch to tell it about the environment in
@@ -2765,6 +2895,8 @@ void g_canvas_setup(void)
     class_addcreator((t_newmethod)table_new, gensym("table"),
         A_DEFSYM, A_DEFFLOAT, 0);
 
+    class_addmethod(canvas_class, (t_method)canvas_showdirty,
+        gensym("showdirty"), 0);
 /*---------------------------- declare ------------------- */
     declare_class = class_new(gensym("declare"), (t_newmethod)declare_new,
         (t_method)declare_free, sizeof(t_declare), CLASS_NOINLET, A_GIMME, 0);
diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h
index a5d7ed1166c0a9cb192c3c809af20e649e8e49aa..44ee4e4ab581c2ae9ea79e8ef07292400260780a 100644
--- a/pd/src/g_canvas.h
+++ b/pd/src/g_canvas.h
@@ -235,6 +235,7 @@ struct _glist
     t_gpointer gl_gp;            /* parent for "canvas" data type */
 
     int gl_subdirties;     /* number of descending dirty abstractions */
+    int gl_dirties;        /* number of diry instances, for multiple dirty warning */
 };
 
 #define gl_gobj gl_obj.te_g
@@ -581,6 +582,7 @@ EXTERN int canvas_setdeleting(t_canvas *x, int flag);
 EXTERN int canvas_hasarray(t_canvas *x);
 EXTERN int canvas_has_scalars_only(t_canvas *x);
 
+EXTERN void canvas_warning(t_canvas *x, int warid);
 
 #define LB_LOAD 0       /* "loadbang" actions - 0 for original meaning */
 #define LB_INIT 1       /* loaded but not yet connected to parent patch */
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index 295f13bb2b1ff701da2488b86c9ffeda970744bf..50f8d035ca87cdd2b14f98c56e1721c33f68764b 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -2752,7 +2752,7 @@ void canvas_vis(t_canvas *x, t_floatarg f)
                We may need to expand this to include scalars, as well. */
             canvas_create_editor(x);
             canvas_args_to_string(argsbuf, x);
-            gui_vmess("gui_canvas_new", "xiisiiissiiiis",
+            gui_vmess("gui_canvas_new", "xiisiiissiiiiis",
                 x,
                 (int)(x->gl_screenx2 - x->gl_screenx1),
                 (int)(x->gl_screeny2 - x->gl_screeny1),
@@ -2763,6 +2763,9 @@ void canvas_vis(t_canvas *x, t_floatarg f)
                 x->gl_name->s_name,
                 canvas_getdir(x)->s_name,
                 x->gl_dirty,
+                (x->gl_dirties > 1 ?
+                    (x->gl_dirty ? 2 : 1)
+                    : (x->gl_dirties ? !x->gl_dirty : 0)),
                 x->gl_noscroll,
                 x->gl_nomenu,
                 canvas_hasarray(x),
@@ -6171,6 +6174,12 @@ void gobj_dirty(t_gobj *x, t_glist *g, int state)
     t_rtext *y = glist_findrtext(g, (t_text *)x);
     gui_vmess("gui_gobj_dirty", "xsi", g, rtext_gettag(y), state);
 }
+    /* tell the gui to display a specific message in the
+        top right corner */
+void canvas_warning(t_canvas *x, int warid)
+{
+    gui_vmess("gui_canvas_warning", "xi", x, warid);
+}
 
 static int glist_dofinderror(t_glist *gl, void *error_object)
 {