diff --git a/common/decorators.py b/common/decorators.py
index 7c1f4d85f75fdb2f0296f0a8e9c304767b963264..729224c2d66e02b2958a3fdd2d2c9a24b513bf60 100644
--- a/common/decorators.py
+++ b/common/decorators.py
@@ -29,8 +29,7 @@ def json_decode_fallback(value):
 def ajax(login_required=False, method=None, encode_result=True):
     def decorator(fun):
         @wraps(fun)
-        def ajax_view(request):
-            kwargs = {}
+        def ajax_view(request, *args, **kwargs):
             request_params = None
             if method == 'post':
                 request_params = request.POST
@@ -43,11 +42,11 @@ def ajax(login_required=False, method=None, encode_result=True):
                                       if fun_kwargs or key in fun_params)
                 kwargs.update(stringify_keys(request_params))
             res = None
-            if login_required and not request.user.is_authenticated():
+            if login_required and not request.user.is_authenticated:
                 res = {'result': 'logout'}
             if not res:
                 try:
-                    res = fun(request, **kwargs)
+                    res = fun(request, *args, **kwargs)
                 except AjaxError as e:
                     res = {'result': e.args[0]}
                     transaction.rollback()
diff --git a/common/migrations/__init__.py b/common/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/common/static/common/js/utils.js b/common/static/common/js/utils.js
index 8e8763c8ea943aa416c74b3fb6d32c7ab5f47464..ff58cb14da15a71973043d8dac662422f52d42ff 100644
--- a/common/static/common/js/utils.js
+++ b/common/static/common/js/utils.js
@@ -7,6 +7,11 @@ jQuery.fn.immediateText = function() {
     return this.contents().not(this.children()).text();
 };
 
+jQuery.urlParam = function (name) {
+    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.search);
+    return (results !== null) ? results[1] || 0 : false;
+}
+
 function tooltipped_span(text, tooltip_text, cls) {
     var html = tooltip_text.includes('<') ? ' data-html="true"' : '';
     cls = cls ? ' class="' + cls + '"' : '';
diff --git a/connections/migrations/__init__.py b/connections/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/entries/static/entries/js/entries.js b/entries/static/entries/js/entries.js
index 6970a543d94caec865b6697e1821a9608dd91411..4e9ab7d93f59ce8f119729f9f1e05b199810b01b 100644
--- a/entries/static/entries/js/entries.js
+++ b/entries/static/entries/js/entries.js
@@ -795,13 +795,13 @@ function show_unmatched_examples() {
     }
 }
 
-function get_entry(entry_id, related) {
+function get_entry(entry_id, related, lexical_unit_id) {
     check_import_status();
     clear_entry();
     show_entry_spinners();
     //var data = { 'forms' : serialize_forms($('#main-form')), 'entry' : entry_id };
-    var data = { 'entry' : entry_id, 'no_filters' : related };
-    $.ajax({
+    var data = { 'entry' : entry_id, 'no_filters' : related, 'lexical_unit_id': lexical_unit_id };
+    return $.ajax({
         type     : 'post',
         url      : '/' + lang + '/entries/get_entry/',
         dataType : 'json',
@@ -822,7 +822,7 @@ function get_entry(entry_id, related) {
             show_syntax(response.subentries);
             show_semantics(response.frames, response.subentries);
             show_unmatched_examples();
-            
+
             // if current tab is empty, switch to an active tab
             var active_tab = $('#entryTabs').find('.nav-link.active').attr('id');
             if (active_tab === 'semantics-tab' && $('.frame').length === 0) {
@@ -837,7 +837,7 @@ function get_entry(entry_id, related) {
                     $('#syntax-tab').tab('show');
                 }
             }
-            
+
             // tooltips with meaning gloss
             activate_tooltips($('.frame'));
             // tooltips with phrase descriptions
diff --git a/entries/static/entries/js/selectional_preference.js b/entries/static/entries/js/selectional_preference.js
index a802f135a18c7ecc89d74e3c3f70833b95904a7e..52cf001acdfaadb253c0e07c5a887fbd3512e7ea 100644
--- a/entries/static/entries/js/selectional_preference.js
+++ b/entries/static/entries/js/selectional_preference.js
@@ -148,7 +148,7 @@ function addSelectivePreferenceBase(frame, complement_id) {
         state0: {
             title: 'Typ preferencji selekcyjnej',
             html: 'Wybierz typ preferencji selekcyjnej',
-            buttons: { Predefiniowana: 1, Słowosieć: 2, Relacja: 3, Koniec: -1 },
+            buttons: { Istniejąca: 0, Predefiniowana: 1, Słowosieć: 2, Relacja: 3, Koniec: -1 },
             focus: -1,
             submit: function(e,v,m,f){
                 if (v == -1) {
@@ -158,20 +158,21 @@ function addSelectivePreferenceBase(frame, complement_id) {
 
                     $.prompt.close();
                 }
+                if (v === 0) {
+                    e.preventDefault();
+                    $.prompt.goToState('state4');
+                }
                 if (v == 1) {
                     e.preventDefault();
                     $.prompt.goToState('state1');
-
                 }
                 if (v == 2) {
                     e.preventDefault();
                     $.prompt.goToState('state2');
-
                 }
                 if (v == 3) {
                     e.preventDefault();
                     $.prompt.goToState('state3');
-
                 }
             }
         },
@@ -241,6 +242,25 @@ function addSelectivePreferenceBase(frame, complement_id) {
                         }
                     }
 
+                    $.prompt.goToState('state0');
+                }
+            }
+        },
+        state4: {
+            title: 'Wybierz z istniejÄ…cych',
+            html: relationArgument(frame, complement_id),
+            buttons: { Anuluj: -1, Zatwierdź: 1 },
+            focus: 1,
+            submit: function(e,v,m,f){
+                if (v == -1) {
+                    e.preventDefault();
+                    $.prompt.goToState('state0');
+                }
+                if (v == 1) {
+                    e.preventDefault();
+
+                    // TODO
+
                     $.prompt.goToState('state0');
                 }
             }
diff --git a/entries/static/entries/js/unification.js b/entries/static/entries/js/unification.js
index 516919ea016447ba8e380821f262d9b648054daa..7dc4e68bd05c398c08aba98eaef7ab7ea9ae436e 100644
--- a/entries/static/entries/js/unification.js
+++ b/entries/static/entries/js/unification.js
@@ -49,9 +49,7 @@ function get_unified_frame(unified_frame_id, related) {
             }
 
             // tooltips with meaning gloss
-            activate_tooltips($('.frame'));
-            // tooltips with phrase descriptions
-            activate_tooltips($('.schema'));
+            activate_tooltips($('#semantics-frames-pane'));
             update_last_visited(response.last_visited);
         },
         error: function(request, errorType, errorMessage) {
@@ -146,7 +144,6 @@ function unifiedFrame2dom(unifiedFrame, slowal_frames) {
     tbody.append(preferences_row);
     table.append(tbody);
     div.append(table);
-
     return div;
 }
 
@@ -180,6 +177,7 @@ function frames2lexical_units(frames) {
         for (var j in frame.lexical_units) {
             var lexical_unit = frame.lexical_units[j];
             lexical_unit.opinion = frame.opinion;
+            lexical_unit.opinion_key = frame.opinion_key;
             lexical_units.push(lexical_unit);
         }
     }
@@ -203,7 +201,7 @@ function frames2lexical_unitsHTML(frames) {
         let lexical_unit = lexical_units[j];
         let lexical_unit_row = document.createElement('tr');
         lexical_unit_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary">' + gettext(lexical_unit.str) + '</td>';
-        lexical_unit_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary">' + gettext(lexical_unit.opinion) + '</td>';
+        lexical_unit_row.innerHTML += '<td class="argument py-2 px-1 border-top border-left border-secondary">' + '<img src="' + window.STATIC_URL + 'entries/img/' + lexical_unit.opinion_key + '.svg" width="12" height="12" alt="' + lexical_unit.opinion + '"> ' + gettext(lexical_unit.opinion) + '</td>';
         tbody.append(lexical_unit_row);
     }
     table.append(tbody);
diff --git a/entries/static/entries/js/unification_entries_list.js b/entries/static/entries/js/unification_entries_list.js
index cad878f4673672070f6a9e6b8f93a54cba33ecba..4f2fd5b1806865541a24935eb4f92b6619ee709c 100644
--- a/entries/static/entries/js/unification_entries_list.js
+++ b/entries/static/entries/js/unification_entries_list.js
@@ -26,7 +26,6 @@ function update_entries() {
             if (!row.data()) return;
             var drilldown = $("<div>").addClass("drilldown").data("row", this);
             row.child(drilldown).show();
-            // tutaj zmiana na liste zunifikowanych ram
             setup_lexical_units_table(drilldown, row.data().lexical_units, can_see_assignees);
             drilldown.closest("td").addClass("p-0 pl-4");
         }
@@ -77,7 +76,7 @@ function setup_lexical_units_table(drilldown, lexical_units, can_see_assignees)
     });
 }
 
-function setup_notes($container, $template, lexical_unit_pk, entry) {
+function setup_notes($container, $template, lexical_unit_pk, entry_pk) {
     $container.html($template.children().clone());
     $('.show-note-form', $container).click(function () {
         $('.note-form', $container).html($('#note-form-template > div', $container).clone());
@@ -91,7 +90,7 @@ function setup_notes($container, $template, lexical_unit_pk, entry) {
                 data     : { note: $('.note-form textarea[name=note]').val() },
                 timeout  : 5000,
                 success  : function (response) {
-                    get_lexical_unit(lexical_unit_pk, entry);
+                    get_lexical_unit(lexical_unit_pk, entry_pk);
                 },
                 error    : function () {
                     alert(gettext('Nie udało się dodać notatki.'));
@@ -111,7 +110,76 @@ function setup_notes($container, $template, lexical_unit_pk, entry) {
     })
 }
 
-function get_lexical_unit(pk, entry) {
-    get_entry(entry, false);  // TODO replace with loading LU details view
-    setup_notes($('#lexical-unit-notes'), $('#lexical-unit-notes-template'), pk, entry);
+function setup_unified_frame($container, frame, unified_frame, refresh) {
+    $container.html('');
+    if (unified_frame) {
+        var $button_container = $('<div>').addClass("text-center");
+        var $button = $('<a>').addClass('btn btn-sm btn-outline-dark mr-2');
+        function go_to_unified_frame () {
+            window.location = `/${window.lang}/entries/unification_frames`;  // TODO redirect properly
+        }
+        function invalid () {
+            $.ajax({
+                type     : 'post',
+                url      : `/${lang}/semantics/frame_mark_as_invalid/${frame.id}/`,
+                dataType : 'json',
+                timeout  : 60000,
+            }).then(refresh);
+        }
+        function take () {
+            $.ajax({
+                type     : 'post',
+                url      : `/${lang}/semantics/frame_assign/${frame.id}/`,
+                dataType : 'json',
+                timeout  : 60000,
+            }).then(go_to_unified_frame);
+        }
+        function confirm_invalid () {
+            $.ajax({
+                type     : 'post',
+                url      : `/${lang}/semantics/frame_confirm_invalid/${frame.id}/`,
+                dataType : 'json',
+                timeout  : 60000,
+            }).then(refresh);
+        }
+        function reject_invalid () {
+            $.ajax({
+                type     : 'post',
+                url      : `/${lang}/semantics/frame_reject_invalid/${frame.id}/`,
+                dataType : 'json',
+                timeout  : 60000,
+            }).then(refresh);
+        }
+        if (frame.status === 'N') {
+            $button_container.append($button.clone().html(gettext("Błędna")).click(invalid));
+            $button_container.append($button.clone().html(gettext("Pobierz")).click(take));
+        } else if (frame.status === 'O') {
+            $button_container.append($button.clone().html(gettext("Obrabiaj")).click(go_to_unified_frame));
+        } else if (frame.status === 'G' && unified_frame.status === 'O') {
+            $button_container.append($button.clone().html(gettext("Obrabiaj")).click(go_to_unified_frame));
+        } else if (frame.status === 'S' && unified_frame.status === 'S') {
+            $button_container.append($button.clone().html(gettext("Obejrzyj")).click(go_to_unified_frame));
+        } else if (frame.status === 'B' && has_permission('semantics.manage_invalid_lexical_units')) {
+            $button_container.append($button.clone().html(gettext("Potwierdź")).click(confirm_invalid));
+            $button_container.append($button.clone().html(gettext("Odrzuć")).click(reject_invalid));
+        }
+        $container.append($button_container);
+    } else {
+        $container.append($('<p>').html(gettext("Brak ramy unifikacyjnej.")));
+    }
+}
+
+function get_lexical_unit(lexical_unit_pk, entry_pk) {
+    $('#lexical-unit-notes').html('');
+    $('#unified-frame').html('');
+    get_entry(entry_pk, false, lexical_unit_pk).then(function (entry) {
+        var frame = entry.frames[0];
+        setup_unified_frame(
+            $('#unified-frame'),
+            frame,
+            entry.unified_frame,
+            function () { get_lexical_unit(lexical_unit_pk, entry_pk); update_entries(); }
+        );
+        setup_notes($('#lexical-unit-notes'), $('#lexical-unit-notes-template'), lexical_unit_pk, entry_pk);
+    });
 }
diff --git a/entries/templates/unification_lexical_unit_display.html b/entries/templates/unification_lexical_unit_display.html
index cd6bcb568d4bdcfa6faaa5444cf63085649fc721..1668b5264a07dcc0715424a3eab416cddb5b3e85 100644
--- a/entries/templates/unification_lexical_unit_display.html
+++ b/entries/templates/unification_lexical_unit_display.html
@@ -5,6 +5,7 @@
         <div class="row m-0 p-0" id="semantics-top-pane">
             <div class="col h-100 px-1 pt-0 pb-0 overflow-auto" id="semantics-frames-pane">
                 <div id="semantics-frames"></div>
+                <div id="unified-frame" class="mb-3"></div>
                 <div id="lexical-unit-notes-template" class="d-none">{% include 'notes.html' %}</div>
                 <div id="lexical-unit-notes"></div>
             </div>
diff --git a/entries/views.py b/entries/views.py
index bd41a844fbdef104ffbd6d82409eb64fe4f44078..f238216ba7e51ff5a08c89b9781fac1a019a3841 100644
--- a/entries/views.py
+++ b/entries/views.py
@@ -22,6 +22,7 @@ from syntax.models import NaturalLanguageDescription, Schema
 from semantics.models import Frame, PredefinedSelectionalPreference, SelectivePreferenceRelations
 
 from common.decorators import ajax_required, ajax
+from unifier.models import UnifiedFrame
 
 from .forms import (
     EntryForm,
@@ -415,9 +416,9 @@ def get_entries(request):
                                     'pk': lu.pk,
                                     'display': str(lu),
                                     'assignee_username': (
-                                        assignment.user.username if (assignment := lu.assignments.first()) else None
+                                        assignment.user.username if (frame := lu.frames.first()) and (assignment := frame.assignments.first()) else None
                                     ),
-                                    'status': frame.status if (frame := lu.frames.first()) else ""
+                                    'status': frame.status if (frame := lu.frames.first()) else "",
                                 } for lu in e.lexical_units.all()
                             ]
                         }
@@ -540,6 +541,7 @@ def frame2dict(frame, entry_meanings):
         'opinion'       : FRAME_OPINION()[frame.opinion.key],
         'opinion_key'   : frame.opinion.key,
         'id'            : frame.id,
+        'status'        : frame.status,
         'lexical_units' : [
             {
                 'str'           : lu.text_rep,
@@ -732,6 +734,7 @@ def get_entry(request):
             apply_filters = not simplejson.loads(request.POST['no_filters'])
             filter_schemata = apply_filters and entry_form.cleaned_data['filter_schemata']
             filter_frames = apply_filters and entry_form.cleaned_data['filter_frames']
+            lexical_unit = LexicalUnit.objects.get(pk=lu_id) if (lu_id := request.POST.get("lexical_unit_id")) else None
             if filter_schemata:
                 schema_forms = []
                 # e.g. entry has schema that satisfies X & entry has schema that satisfies Y
@@ -803,18 +806,26 @@ def get_entry(request):
             # filter out frames by frame properties
             if filter_frames:
                 frame_objects = get_filtered_objects2(frame_forms, frame_objects)
+            if lexical_unit:
+                frame_objects = frame_objects.filter(lexical_units=lexical_unit)
             if local_frame_form:
                 frame_objects = get_filtered_objects(local_frame_form, frame_objects)
             frames = [frame2dict(frame, entry.lexical_units.all()) for frame in frame_objects]
             alternations, realisation_phrases, realisation_descriptions = get_alternations(all_schema_objects, frame_objects)
             examples = get_examples(entry)
+            unified_frame = None
+            if lexical_unit and (unified_frame := UnifiedFrame.objects.all().for_lexical_unit(lexical_unit).first()):
+                unified_frame = {
+                    'pk': unified_frame.pk,
+                    'status': unified_frame.status,
+                }
             # https://docs.djangoproject.com/en/2.2/topics/http/sessions/#when-sessions-are-saved
             if [entry.name, entry.id] in request.session['last_visited']:
                 request.session['last_visited'].remove([entry.name, entry.id])
             request.session['last_visited'].insert(0, (entry.name, entry.id))
             request.session['last_visited'] = request.session['last_visited'][:(MAX_LAST_VISITED + 1)]
             request.session.modified = True
-            return JsonResponse({ 'subentries' : subentries, 'frames' : frames, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'last_visited' : request.session['last_visited'] })
+            return JsonResponse({ 'subentries' : subentries, 'frames' : frames, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'unified_frame': unified_frame, 'last_visited' : request.session['last_visited'] })
     return JsonResponse({})
 
 '''
diff --git a/examples/migrations/__init__.py b/examples/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/meanings/migrations/__init__.py b/meanings/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/meanings/models.py b/meanings/models.py
index 3eaaabf2fc0b4eb360b98091e11248ec4975bebb..7a46b0a189ca129a82609ed3c9e6f893d1b0f00f 100644
--- a/meanings/models.py
+++ b/meanings/models.py
@@ -1,4 +1,3 @@
-from django.contrib.contenttypes.fields import GenericRelation
 from django.db import models
 
 
@@ -13,7 +12,6 @@ class LexicalUnit(models.Model):
     definition = models.TextField(default='')
     gloss = models.TextField(default='')
     text_rep = models.TextField()
-    assignments = GenericRelation("users.Assignment", content_type_field="subject_ct", object_id_field="subject_id")
 
     class Meta:
         unique_together = ('base', 'sense', 'pos',)
diff --git a/meanings/views.py b/meanings/views.py
index 91ea44a218fbd2f408430959283f0419c921093e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/meanings/views.py
+++ b/meanings/views.py
@@ -1,3 +0,0 @@
-from django.shortcuts import render
-
-# Create your views here.
diff --git a/phrase_expansions/migrations/__init__.py b/phrase_expansions/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/semantics/choices.py b/semantics/choices.py
index d248493f6ee99ce0ad2417636c1fbe7f2b593730..2d644ab334711dccf2e8ada0855c350872397bae 100644
--- a/semantics/choices.py
+++ b/semantics/choices.py
@@ -2,7 +2,10 @@ from django.db import models
 from django.utils.translation import gettext_lazy as _
 
 
-class LexicalUnitStatus(models.TextChoices):
+class FrameStatus(models.TextChoices):
+    NEW = "N", _("nowa")
     PROCESSING = "O", _("w obróbce")
     READY = "G", _("gotowe")
     VERIFIED = "S", _("sprawdzone")
+    INVALID = "B", _("błędna")  # set by lexicographs
+    BAD = "Z", _("zła")  # confirmed by super lexicographs
diff --git a/semantics/migrations/__init__.py b/semantics/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/semantics/models.py b/semantics/models.py
index c746064473c4caa2e02cf1e88001e77c27209a93..e9ddba0ab0c6d555ecfbfecbccde0ed486309afc 100644
--- a/semantics/models.py
+++ b/semantics/models.py
@@ -1,3 +1,4 @@
+from django.contrib.contenttypes.fields import GenericRelation
 from django.db import models
 
 from meanings.models import LexicalUnit, Synset
@@ -11,9 +12,10 @@ class Frame(models.Model):
     arguments_count = models.PositiveIntegerField(null=False, default=0) #na potrzeby filtrowania
     status = models.TextField(
         max_length=10,
-        choices=choices.LexicalUnitStatus.choices,
-        default=choices.LexicalUnitStatus.PROCESSING,
+        choices=choices.FrameStatus.choices,
+        default=choices.FrameStatus.NEW,
     )
+    assignments = GenericRelation("users.Assignment", content_type_field="subject_ct", object_id_field="subject_id")
 
     def sorted_arguments(self):  # TODO: zaimplementowac wlasciwe sortowanie
         return Argument.objects.filter(frame=self)
diff --git a/semantics/urls.py b/semantics/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..48d7e8569b2705f16df8dc77f8f8375a122231ff
--- /dev/null
+++ b/semantics/urls.py
@@ -0,0 +1,12 @@
+from django.urls import path
+
+from . import views
+
+app_name = 'semantics'
+
+urlpatterns = [
+    path('frame_mark_as_invalid/<int:frame_pk>/', views.frame_mark_as_invalid, name='frame_mark_as_invalid'),
+    path('frame_assign/<int:frame_pk>/', views.frame_assign, name='frame_assign'),
+    path('frame_confirm_invalid/<int:frame_pk>/', views.frame_confirm_invalid, name='frame_confirm_invalid'),
+    path('frame_reject_invalid/<int:frame_pk>/', views.frame_reject_invalid, name='frame_reject_invalid'),
+]
diff --git a/semantics/views.py b/semantics/views.py
index 91ea44a218fbd2f408430959283f0419c921093e..719f55e2cc916f80a2ba7d48798f6f68fc046c22 100644
--- a/semantics/views.py
+++ b/semantics/views.py
@@ -1,3 +1,52 @@
-from django.shortcuts import render
+from django.contrib.auth.decorators import permission_required
+from django.core.exceptions import ValidationError
+from django.db import transaction
+from django.shortcuts import get_object_or_404
 
-# Create your views here.
+from common.decorators import ajax
+from users.models import Assignment
+
+from . import choices
+from .models import Frame
+
+
+@ajax(login_required=True, method='post')
+@transaction.atomic
+def frame_mark_as_invalid(request, frame_pk):
+    frame = get_object_or_404(Frame.objects.filter(status=choices.FrameStatus.NEW).select_for_update(), pk=frame_pk)
+    frame.status = choices.FrameStatus.INVALID
+    frame.save()
+    return {}
+
+
+@ajax(login_required=True, method='post')
+@transaction.atomic
+def frame_assign(request, frame_pk):
+    frame = get_object_or_404(
+        Frame.objects.filter(status=choices.FrameStatus.NEW).select_for_update(),
+        pk=frame_pk,
+    )
+    frame.status = choices.FrameStatus.PROCESSING
+    Assignment.assign(user=request.user, subject=frame)
+    frame.save()
+    return {}
+
+
+@ajax(login_required=True, method='post')
+@transaction.atomic
+@permission_required('semantics.manage_invalid_lexical_units')
+def frame_confirm_invalid(request, frame_pk):
+    frame = get_object_or_404(Frame.objects.filter(status=choices.FrameStatus.INVALID).select_for_update(), pk=frame_pk)
+    frame.status = choices.FrameStatus.BAD
+    frame.save()
+    return {}
+
+
+@ajax(login_required=True, method='post')
+@transaction.atomic
+@permission_required('semantics.manage_invalid_lexical_units')
+def frame_reject_invalid(request, frame_pk):
+    frame = get_object_or_404(Frame.objects.filter(status=choices.FrameStatus.INVALID).select_for_update(), pk=frame_pk)
+    frame.status = choices.FrameStatus.NEW
+    frame.save()
+    return {}
diff --git a/shellvalier/urls.py b/shellvalier/urls.py
index 985cf8e775a3c636ae593934bf3d41250d12ee69..9f5e11f22bdd76b9a2f319160ef0d955a0bc62b4 100644
--- a/shellvalier/urls.py
+++ b/shellvalier/urls.py
@@ -16,6 +16,7 @@ urlpatterns = i18n_patterns(
     path('users/', include('users.urls')),
     path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
     path('admin/', admin.site.urls, name='admin'),
+    path('semantics/', include('semantics.urls')),
     path('unifier/', include('unifier.urls')),
     path('', dash, name='dash'),
     # uncomment to leave default (Polish) urls unchanged
diff --git a/syntax/migrations/__init__.py b/syntax/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/unifier/choices.py b/unifier/choices.py
new file mode 100644
index 0000000000000000000000000000000000000000..756468815f3a83c24d521a9f400779f7f32661f9
--- /dev/null
+++ b/unifier/choices.py
@@ -0,0 +1,8 @@
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+
+class UnifiedFrameStatus(models.TextChoices):
+    PROCESSING = "O", _("w obróbce")
+    READY = "G", _("gotowe")
+    VERIFIED = "S", _("sprawdzone")
diff --git a/unifier/migrations/__init__.py b/unifier/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/unifier/models.py b/unifier/models.py
index fc2d74a20ad8b57d6ebf0ddd1ece8a3f2b5900fe..2408c485d44b4c48e57f18d926b75d852a3be946 100644
--- a/unifier/models.py
+++ b/unifier/models.py
@@ -1,23 +1,45 @@
-from django.db import models
+from copy import deepcopy
+from typing import List, Optional
+
+from django.db import models, transaction
 
 from meanings.models import LexicalUnit, Synset
-from semantics.models import choices, PredefinedSelectionalPreference, RelationalSelectionalPreference, ArgumentRole, \
+from semantics.models import PredefinedSelectionalPreference, RelationalSelectionalPreference, ArgumentRole, \
     RoleType, Argument, Frame, SelectionalPreferenceRelation
 
+from . import choices
+
+
+class UnifiedFrameQueryset(models.QuerySet):
+    def for_lexical_unit(self, lexical_unit):
+        return self.filter(unified_frame_2_slowal_frame__slowal_frame__lexical_units=lexical_unit)
+
 
 class UnifiedFrame(models.Model):
     status = models.TextField(
         max_length=10,
-        choices=choices.LexicalUnitStatus.choices,
-        default=choices.LexicalUnitStatus.PROCESSING,
+        choices=choices.UnifiedFrameStatus.choices,
+        default=choices.UnifiedFrameStatus.PROCESSING,
     )
     title = models.CharField(max_length=200, default=None, blank=True, null=True)
 
+    objects = models.Manager.from_queryset(UnifiedFrameQueryset)()
+
     def sorted_arguments(self):  # TODO: zaimplementowac wlasciwe sortowanie
         return UnifiedFrameArgument.objects.filter(frame=self)
 
     def __str__(self):
         return '%s: %s' % (self.opinion, ' + '.join([str(arg) for arg in self.sorted_arguments()]))
+    
+    @transaction.atomic
+    def extract_frames_to(
+            self, slowal_frames: List[Frame], new_frame: Optional["UnifiedFrame"] = None
+    ) -> "UnifiedFrame":
+        if not new_frame:
+            new_frame = UnifiedFrame.objects.create(title=self.title)
+        raise NotImplementedError()  # TODO move slowal frames to `new_frame`
+        return new_frame
+
 
 class UnifiedFrameArgument(models.Model):
     id = models.CharField(max_length=20, primary_key=True)
diff --git a/unifier/views.py b/unifier/views.py
index a114f106e47631c7313b783f439743ad878d923b..c9426e74851b965d0ce5b4a6e44f720c67060b61 100644
--- a/unifier/views.py
+++ b/unifier/views.py
@@ -68,7 +68,7 @@ def get_unified_frames(request):
                 'title': title,
                 'status': value[0].unified_frame.status,
                 'assignee_username': (
-                    assignment.user.username if (assignment := value[0].slowal_frame.lexical_units.first().assignments.first()) else None
+                    assignment.user.username if (assignment := value[0].slowal_frame.assignments.first()) else None
                 ),
             })
 
diff --git a/users/management/commands/create_groups_and_permissions.py b/users/management/commands/create_groups_and_permissions.py
index d6f71f3d2402252801da7f4b7e0d72e0c2d14684..b05773230c9550175225e71490a9f4c67b77c9d2 100644
--- a/users/management/commands/create_groups_and_permissions.py
+++ b/users/management/commands/create_groups_and_permissions.py
@@ -3,6 +3,7 @@ from django.contrib.auth.models import Group, Permission, User
 from django.contrib.contenttypes.models import ContentType
 from django.core.management.base import BaseCommand
 
+from semantics.models import Frame
 from users.models import Assignment, Note
 
 
@@ -11,7 +12,12 @@ class Command(BaseCommand):
         Permission.objects.update_or_create(
             content_type=ContentType.objects.get_for_model(Note),
             codename="view_all_notes",
-            defaults={"name": "View all notes"}
+            defaults={"name": "View all Notes"}
+        )
+        Permission.objects.update_or_create(
+            content_type=ContentType.objects.get_for_model(Frame),
+            codename="manage_invalid_lexical_units",
+            defaults={"name": "Manage invalid Lexical Units"}
         )
         admins, __ = Group.objects.get_or_create(name='Admini')
         admins.permissions.add(
@@ -30,6 +36,7 @@ class Command(BaseCommand):
         super_lexicographs.permissions.add(
             self._get_permission(Assignment, 'view_assignment'),
             self._get_permission(Note, 'view_all_notes'),
+            self._get_permission(Frame, 'manage_invalid_lexical_units'),
         )
 
     def _get_permission(self, model, codename) -> Permission:
diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/users/models.py b/users/models.py
index 10496b8a218808f581b6e074d3e3d111be7e6222..df1755846d324e36822827e7fa5f6e4c3a4dd879 100644
--- a/users/models.py
+++ b/users/models.py
@@ -16,6 +16,14 @@ class Assignment(models.Model):
             ("subject_ct", "subject_id"),
         ]
 
+    @classmethod
+    def assign(cls, user, subject):
+        Assignment.objects.get_or_create(
+            user=user,
+            subject_ct=ContentType.objects.get_for_model(subject),
+            subject_id=subject.pk,
+        )
+
 
 class NoteQuerySet(models.QuerySet):
     def for_user(self, user):