diff --git a/capsule-prototype/index.html b/capsule-prototype/index.html
index 74e3481c0374b510c4ad6ab122df2224f6b87666..7ad8498a28c865808909621e4d94c9849c581c55 100644
--- a/capsule-prototype/index.html
+++ b/capsule-prototype/index.html
@@ -29,6 +29,7 @@
 
 	<script language="javascript" type='text/javascript' src="../shared/php/rekallApp.js.php"></script>
 
+	<script language="javascript" type="text/javascript" src="../shared/js/online-rekall/NotificationEntityChanged.js" ></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/rekall/Utils.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/online-rekall/Rekall.js'></script>
 	<script language="javascript" type='text/javascript' src='../shared/js/rekall/Source.js'></script>
diff --git a/capsule-prototype/js/MosaicPanelWidget.js b/capsule-prototype/js/MosaicPanelWidget.js
index d9c8d41e287e9a813908362197d318cd6ddd686e..3b51e18a8a48fba1d4301d947360518cdcd0d387 100644
--- a/capsule-prototype/js/MosaicPanelWidget.js
+++ b/capsule-prototype/js/MosaicPanelWidget.js
@@ -28,11 +28,37 @@
                 },
                 rekall: function() {
                     return app.rekall.Rekall('rekall');
-                }
+                },
+                _filters_to_hide: [],
+                _followedItems: []
             };
 
             const localOptions = $.extend({}, defaultOptions);
 
+            // eslint-disable-next-line no-unused-vars
+            localOptions._resetFilters = function(event) {
+                    $('.mosaic_filter_item')
+                        .removeClass('mosaic_filter_disabled');
+                    $('.mosaic_label_filter_enabled').click();
+                    localOptions._filters_to_hide = [];
+                    $('.mosaic_category').show();
+            }
+
+            localOptions._destroyFollowedElements = function () {
+                let elementsToDestroy = localOptions._followedItems;
+                localOptions._followedItems = [];
+                elementsToDestroy.forEach(e => e.remove());
+            }
+
+            function toggleSelectedFilter(filterName){
+                let filters = localOptions._filters_to_hide;
+                if (filters.includes(filterName)){
+                    filters.splice(filters.indexOf(filterName), 1);
+                } else {
+                    filters.push(filterName);
+                }
+            }
+
             const getUrl = function (tagOrDoc, path) {
                 if ('undefined' === typeof path) {
                     let [reg, type] = tagOrDoc.getMetadata("Rekall->Type").split('/');
@@ -45,6 +71,14 @@
                 }
             }
 
+            const computeLabelShowing = function(div, tag) {
+                if (tag.getLabels().filter(value => localOptions._filters_to_hide.includes(value.toLowerCase())).length){
+                    div.hide();
+                } else {
+                    div.show()
+                }
+            }
+
             const createHtmlElementForTagOrDoc = function (tagOrDoc) {
                 let path = localOptions.getPreviewPath(tagOrDoc);
                 let name = tagOrDoc.getMetadata("Rekall->Name");
@@ -64,20 +98,49 @@
                 div.append($('<span/>').addClass('caption').text(name));
                 div.attr('data-rekall-labels', '');
                 div.attr('data-rekall-labels', labels.toLowerCase());
+
+                let divUpdater = {
+                    div: div,
+                    tag: tagOrDoc
+                };
+                divUpdater.update = function(tag){
+                    div.attr('data-rekall-labels', tag.getMetadata('Rekall->Labels') || '')
+                    computeLabelShowing(div, tag);
+                }
+                divUpdater.remove = function () {
+                    tagOrDoc.removeObserver(divUpdater);
+                }
+                divUpdater.computeVisibility = function (){
+                    computeLabelShowing(divUpdater.div, divUpdater.tag);
+                }
+                tagOrDoc.addObserver(divUpdater);
+                localOptions._followedItems.push(divUpdater);
+
+                computeLabelShowing(div, tagOrDoc);
+
                 return div;
             }
 
-            const getFilterElement = function (text, color, callback, css_class) {
-                return $('<div/>')
+            const getFilterElement = function (text, color, callback, css_class, isToggled) {
+                let result = $('<div/>')
                     .addClass('mosaic_filter_item mosaic_button')
                     .addClass(css_class)
                     .on('click', callback)
                     .css('background-color', color)
                     .append($('<h2/>').text(text));
+
+
+                if(isToggled){
+                    result.toggle();
+                    result.toggleClass('mosaic_filter_disabled');
+                }
+
+                return result;
             }
 
             const emptyPanel = function () {
                 localOptions.htmlElement().html('');
+                localOptions._destroyFollowedElements();
             }
 
             const fillPanel = function () {
@@ -85,11 +148,9 @@
                 filterdiv.append(getFilterElement(
                     'Reset filters',
                     'rgb(100,100,100)',
-                    function () {
-                        $('.mosaic_filter_item').removeClass('mosaic_filter_disabled');
-                        $('.mosaic_category').show();
-                    },
-                    'mosaic_filter_item_all'
+                    localOptions._resetFilters,
+                    'mosaic_filter_item_all',
+                    false
                 ));
                 localOptions.htmlElement().append(filterdiv);
                 let labels = new Set();
@@ -108,54 +169,50 @@
                         v.color, function () {
                             category.toggle();
                             $(this).toggleClass('mosaic_filter_disabled');
+                            localOptions.toggle_filter(categoryName)
                         },
-                        ''
+                        '',
+                        localOptions._filters_to_hide.includes(categoryName)
                     ));
 
                     for (let i in v.tags) {
                         grid.append(createHtmlElementForTagOrDoc(v.tags[i]));
-                        let current_labels = v.tags[i].getMetadata('Rekall->Labels');
-                        if (current_labels && current_labels != '') {
-                            current_labels.split(';').forEach(l => {
-                                if (l != '') labels.add(l)
-                            });
-                        }
+                        v.tags[i].getLabels().forEach(l => labels.add(l));
                     }
                     localOptions.htmlElement().append(category);
                 }
 
                 labels.forEach(l => {
                     let button = $('<div/>').html(l);
-                    button.my_state = undefined;
+                    button.my_state = localOptions._filters_to_hide.includes(l.toLowerCase());
                     button.addClass('mosaic_filter_item mosaic_button mosaic_label_filter');
+                    if(button.my_state) {
+                        button.toggleClass('mosaic_label_filter_enabled');
+                    }
                     button.click(ev => {
-                        if (typeof (ev.target.my_state) === 'undefined') ev.target.my_state = true;
-                        else ev.target.my_state = !ev.target.my_state;
-                        if (ev.target.my_state) $(ev.target).addClass('mosaic_label_filter_enabled');
-                        else $(ev.target).removeClass('mosaic_label_filter_enabled');
+                        ev.target.my_state = !ev.target.my_state;
+                        localOptions.toggle_filter(l.toLowerCase());
+                        $(ev.target).toggleClass('mosaic_label_filter_enabled');
                         let labels = new Set();
                         $('.mosaic_label_filter').toArray().forEach(f => {
-                            if (f.my_state == true)
+                            if (true === f.my_state)
                                 labels.add(f.innerText.toLowerCase());
                         });
                         if (!labels.size) {
                             $('.mosaic_label_filter').toArray().forEach(f => {
-                                f.my_state = undefined;
+                                f.my_state = false;
                                 $(f).removeClass('mosaic_label_filter_enabled');
                             });
                         }
                         console.debug('filters: ', labels);
-                        $('.mosaic_item').toArray().forEach(t => {
-                            if (!labels.size || Array.from(labels).map(l => t.dataset.rekallLabels.indexOf(l.toLowerCase()) != -1).includes(true))
-                                $(t).show();
-                            else
-                                $(t).hide();
-                        });
+                        localOptions._followedItems.forEach(value => value.computeVisibility())
                     })
                     filterdiv.append(button);
                 });
             }
 
+            localOptions.toggle_filter = toggleSelectedFilter;
+
             return {
                 show: function () {
                     if (localOptions.isOpen)
@@ -176,6 +233,8 @@
                     localOptions.htmlElement().hide();
                     emptyPanel();
                     localOptions.isOpen = false;
+                    localOptions._destroyFollowedElements();
+                    localOptions._filters_to_hide = [];
                 },
                 refresh: function () {
                     emptyPanel();
diff --git a/capsule-prototype/js/PopupPanelWidget.js b/capsule-prototype/js/PopupPanelWidget.js
index f73e5c1dd0e9271e0fa0decadfee371fc7ebff4d..680065eac293cac7677673b25ef19c5f8ef604dc 100644
--- a/capsule-prototype/js/PopupPanelWidget.js
+++ b/capsule-prototype/js/PopupPanelWidget.js
@@ -655,12 +655,8 @@ AnnotationTagPanelEdition.prototype = Object.create(PanelEdition.prototype);
 AnnotationTagPanelEdition.prototype.appendInputEditorDiv = function (parentDiv) {
     let that = this;
 
-    function getLabelArray(tag) {
-        let labels = tag.document.project.labels;
-        if (!labels)
-            return [];
-
-        return labels;
+    function getProjectLabels(tag) {
+        return tag.document.getLabels();
     }
 
     function appendTagInputWithLabel(panel, currentLabel, panelContainsLabel) {
@@ -719,7 +715,7 @@ AnnotationTagPanelEdition.prototype.appendInputEditorDiv = function (parentDiv)
 
             that.tagCreationDiv.css('border', 'initial');
             let labelToAdd = $(this).val();
-            let labels = getLabelArray(that.tag);
+            let labels = getProjectLabels(that.tag);
             if (labels.indexOf(labelToAdd) === -1) {
                 that.tag.document.project.labels.push(labelToAdd);
                 appendTagInputWithLabel(that, labelToAdd);
@@ -736,9 +732,8 @@ AnnotationTagPanelEdition.prototype.appendInputEditorDiv = function (parentDiv)
         .addClass('flex-col popupInput popupInputNoHide')
         .attr('id', 'popupLabelsInput');
 
-    let _labels = getLabelArray(this.tag);
-    let tagLabels = that.tag.getMetadata('Rekall->Labels');
-    tagLabels = tagLabels && tagLabels.trim().length ? tagLabels.slice(';') :  [];
+    let _labels = getProjectLabels(this.tag);
+    let tagLabels = that.tag.getLabels();
     for (let currentLabel of _labels) {
         if (!this.canEdit && !tagLabels.includes(currentLabel))
             continue;
diff --git a/capsule-prototype/js/online-rekall/NotificationEntityChanged.js b/capsule-prototype/js/online-rekall/NotificationEntityChanged.js
new file mode 100644
index 0000000000000000000000000000000000000000..3585de3ab74febd82507b070caab408bd274c6a5
--- /dev/null
+++ b/capsule-prototype/js/online-rekall/NotificationEntityChanged.js
@@ -0,0 +1,27 @@
+function NotificationEntityChanged(data) {
+    this.__observers = [];
+}
+
+NotificationEntityChanged.prototype.notifyChange = function() {
+    let observers = this.__observers;
+    observers.forEach(o => { if (o.update && typeof o.update === 'function') { o.update(this) } });
+};
+
+NotificationEntityChanged.prototype.addObserver = function (observer) {
+    if (!this.__observers.includes(observer)) {
+        this.__observers.push(observer)
+    }
+};
+
+NotificationEntityChanged.prototype.removeObserver = function (observer) {
+    if (!this.__observers.includes(observer)) {
+        this.__observers.slice(this.__observers.indexOf(observer), 1);
+    }
+};
+
+
+NotificationEntityChanged.prototype.getObserver = function (index) {
+    if (index > -1 && index < this.__observers.length) {
+        return this.__observers[index];
+    }
+};
\ No newline at end of file
diff --git a/capsule-prototype/js/online-rekall/Project.js b/capsule-prototype/js/online-rekall/Project.js
index 7ec93b4ec09d6e1316f416bedc0900be41177ae5..1dca2682fe864b71c7fbae8de227d94124b02ab3 100644
--- a/capsule-prototype/js/online-rekall/Project.js
+++ b/capsule-prototype/js/online-rekall/Project.js
@@ -144,17 +144,11 @@ Project.prototype.analyse = function () {
                 this.rekall.sortings["horizontal"].analyseAdd(tag);
                 this.rekall.sortings["colors"].analyseAdd(tag);
                 Tags.push(tag);
-                let labels = tag.getMetadata('Rekall->Labels');
-                if (labels && labels != '') {
-                    let _labels = labels.split(';');
-                    for (let i in _labels) {
-                        let label = _labels[i];
-                        if (label.length === 0) continue;
-                        if (parsed_labels.indexOf(label) != -1)
-                            continue;
-                        parsed_labels.push(label);
+                tag.getLabels().forEach(l => {
+                    if (parsed_labels.indexOf(l) === -1) {
+                        parsed_labels.push(l);
                     }
-                }
+                })
             }
         }
     }
diff --git a/capsule-prototype/js/online-rekall/Tag.js b/capsule-prototype/js/online-rekall/Tag.js
index 155367fce439d57a4deb41231ea6d15d1fc85368..5d0ac8cc30b2abc7162b49663b2458c76ddde113 100644
--- a/capsule-prototype/js/online-rekall/Tag.js
+++ b/capsule-prototype/js/online-rekall/Tag.js
@@ -23,6 +23,7 @@
 */
 
 function Tag(document) {
+    NotificationEntityChanged.call(this, {});
     this.document = document;
     this.documentOriginal = this.document;
     this.version = document.currentVersion;
@@ -34,13 +35,18 @@ function Tag(document) {
     this.thumbnail = undefined;
 }
 
+Tag.prototype = Object.create(NotificationEntityChanged.prototype);
+
+
 Tag.keyToOpenAfterLoading = undefined;
 
 Tag.prototype.getMetadata = function (metadataKey, metadataValue) {
     return this.document.getMetadata(metadataKey, this.version);
 }
 Tag.prototype.setMetadata = function (metadataKey, metadataValue) {
-    return this.document.setMetadata(metadataKey, metadataValue, this.version);
+    let result = this.document.setMetadata(metadataKey, metadataValue, this.version);
+    this.notifyChange();
+    return result;
 }
 Tag.prototype.getMetadatas = function () {
     return this.document.getMetadatas(this.version);
@@ -88,12 +94,17 @@ Tag.prototype.getTimeEnd = function () {
     return this.timeEnd;
 }
 Tag.prototype.setTimeStart = function (val) {
-    if (!isNaN(val))
+    if (!isNaN(val)) {
         this.timeStart = val;
+        this.notifyChange();
+    }
 }
 Tag.prototype.setTimeEnd = function (val) {
-    if (!isNaN(val))
+    if (!isNaN(val)) {
         this.timeEnd = val;
+        this.notifyChange();
+    }
+
 }
 Tag.prototype.isGoodVersion = function () {
     return this.version == this.document.goodVersion;
@@ -176,6 +187,21 @@ Tag.prototype.createTimelineDiv = function () {
     return this.flattenTimelineDom;
 
 }
+Tag.prototype.getLabels = function () {
+    let labels = this.getMetadata('Rekall->Labels');
+    if (!labels)
+        return [];
+
+    let isStringNullOrWhitspace = function (s) {
+        if (typeof s !== "string" || !s) {
+            return false;
+        }
+
+        return !s.trim();
+    }
+
+    return labels.split(';').filter(s => !isStringNullOrWhitspace(s));
+}
 
 function HighLightedTag(data) {
     Tag.call(this, data.Document);
diff --git a/capsule-prototype/js/rekall/Document.js b/capsule-prototype/js/rekall/Document.js
index 926063491c1f0a9906183669d84c30994cbc021a..cb4a3d73cf87af6d07efa22660cd078f05ac9a3b 100644
--- a/capsule-prototype/js/rekall/Document.js
+++ b/capsule-prototype/js/rekall/Document.js
@@ -22,6 +22,7 @@
 */
 
 function Document(data) {
+	NotificationEntityChanged.call(this, {});
 	this.tags      		   = new Array();
 	this.metadatas		   = new Array();
 	this.currentVersion    = 0;
@@ -30,6 +31,9 @@ function Document(data) {
 	this.setMetadata("Rekall->Visibility", "");
 	this.project = data.project;
 }
+
+Document.prototype = Object.create(NotificationEntityChanged.prototype);
+
 Document.prototype.addTag = function(tag) {
 	this.tags.push(tag);
 }
@@ -48,6 +52,7 @@ Document.prototype.getMetadatas = function(version) {
 Document.prototype.setMetadatas = function(metadatas, version) {
 	version = this.initMetadatas();
 	this.metadatas[version] = metadatas;
+	this.notifyChange();
 }
 Document.prototype.isVideoOrAudio = function(version) {
 	return this.isAudio(version) || this.isVideo(version); 
@@ -95,7 +100,13 @@ Document.prototype.getFileName = function() {
   var path = Utils.getLocalFilePath(this, "file");
   return path.replace(/.*r=(.*)/, (f, m) => m);
 }
+Document.prototype.getLabels = function() {
+	let labels = this.project.labels;
+	if (!labels)
+		return [];
 
+	return labels;
+}
 
 
 Document.availableMetadataKeys = new Object();
@@ -121,6 +132,9 @@ Document.prototype.setMetadata = function(metadataKey, metadataValue, version) {
 			Document.availableMetadataKeys[metadataKey] = {count: 0};
 		Document.availableMetadataKeys[metadataKey].count++;
 	}
+	if(currentValue != metadataValue) {
+		this.notifyChange();
+	}
 	return (currentValue != metadataValue);
 }