diff --git a/common/templates/base.html b/common/templates/base.html
index 770ca005330baeaff2b69313cab091cfcf21e247..f39de094022127559c84ab1497c5551df13fc62a 100644
--- a/common/templates/base.html
+++ b/common/templates/base.html
@@ -49,6 +49,7 @@
             <span class="navbar-toggler-icon"></span>
         </button>
         <div class="collapse navbar-collapse" id="navbarNav">
+        {% if request.user.is_authenticated %}
             <ul class="navbar-nav mr-auto">
                 <li class="nav-item" id="nav-entries">
                     <a class="nav-link text-light" href="{% url 'entries:entries' %}">
@@ -74,6 +75,7 @@
                     </li>
                 {% endif %}
             </ul>
+        {% endif %}
         </div>
         <span id="import-status" class="navbar-text text-warning mr-3"></span>
         {% if request.user.is_authenticated %}
diff --git a/dictionary_statistics/views.py b/dictionary_statistics/views.py
index e91161ab6ba7b252ae9aad5505c0dde5cb54fbb8..c681d40d2780f4d54d3ea4d93799b69557b42dde 100644
--- a/dictionary_statistics/views.py
+++ b/dictionary_statistics/views.py
@@ -1,3 +1,4 @@
+from django.contrib.auth.decorators import login_required
 from django.shortcuts import render
 from django.utils.translation import gettext as _
 
@@ -7,6 +8,7 @@ from semantics.models import Frame, FrameOpinion
 
 from entries.polish_strings import POS as POS_names, STATUS, SCHEMA_OPINION, FRAME_OPINION
 
+@login_required
 def dictionary_statistics(request):
     ALL = _('wszystkie')
     
diff --git a/entries/static/entries/js/components/Entries.js b/entries/static/entries/js/components/Entries.js
index 2b92bd810dd3911bfccd977aaae86677741322bd..bc99261cdc6e1f230a9fe6f7f6047888a652649d 100644
--- a/entries/static/entries/js/components/Entries.js
+++ b/entries/static/entries/js/components/Entries.js
@@ -30,7 +30,6 @@ export default {
   mounted () {
     $('#entries-list').length && Split(['#entries-list', '#entry-display'], {
       sizes: [20, 80],
-      minSize: 300,
       gutterSize: 4,
       elementStyle: (dimension, size, gutterSize) => {
         return {
@@ -45,8 +44,8 @@ export default {
     });
   },
   template: `
-    <div id="entries-list" class="col h-100 w-100 px-0">
-      <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-auto">
+    <div id="entries-list" class="col h-100 w-100 px-0 overflow-hidden">
+      <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-hidden">
         <unification-entries-list 
           :unificationEntriesListRefreshKey="unificationEntriesListRefreshKey" 
           :initialLexicalUnitId="lexicalUnitId" 
@@ -54,7 +53,7 @@ export default {
           @lexical-unit-selected="lexicalUnitSelected" />
       </div>
     </div>
-    <div id="entry-display" class="col h-100 p-0">
+    <div id="entry-display" class="col h-100 p-0 overflow-hidden">
       <div class="w-100 h-100">
         <div id="right-pane" class="col w-100 p-0">
           <unification-right-pane 
diff --git a/entries/static/entries/js/components/LexicalUnitDisplay.js b/entries/static/entries/js/components/LexicalUnitDisplay.js
index 183a8103d10f7f2ad49755caec6e952fe5edbb3d..7aae57fade10b4d0e48cdf6d1bc05c56aaed671d 100644
--- a/entries/static/entries/js/components/LexicalUnitDisplay.js
+++ b/entries/static/entries/js/components/LexicalUnitDisplay.js
@@ -94,7 +94,6 @@ export default {
   mounted () {
     Split(['#semantics-frames-pane', '#semantics-schemata-pane'], {
       sizes: [40, 60],
-      minSize: 400,
       gutterSize: 4,
       elementStyle: (dimension, size, gutterSize) => {
         return {
diff --git a/entries/static/entries/js/components/LexicalUnitEdit.js b/entries/static/entries/js/components/LexicalUnitEdit.js
index 9a130e248f422647249905371538761abbc38369..f0442eebb674aa05540eacb9eb7f625513d6a5f4 100644
--- a/entries/static/entries/js/components/LexicalUnitEdit.js
+++ b/entries/static/entries/js/components/LexicalUnitEdit.js
@@ -816,27 +816,26 @@ Object.assign(LexicalUnitEdit, {
     changeShowVerifiedFrames (val) {
       this.showVerifiedFrames = val;
     },
-    getArgumentCSS(argument) {
-      return (argument.role ? argument.role.str + ' ' : '') + (argument == this.active_unified_frame_argument ? 'active' : '');
-    }
   },
   mounted() {
     this.changeStatusButtonTitleToDefault();
-    Split(['#semantics-frames-pane', '#semantics-schemata-pane'], {
-      sizes: [40, 60],
-      minSize: 400,
-      gutterSize: 4,
-      elementStyle: (dimension, size, gutterSize) => {
-        return {
-          'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
-        }
-      },
-    });
-    Split(['#semantics-unified-frame-pane', '#semantics-slowal-frames-pane', '#examples'], {
-      sizes: [40, 40, 20],
-      direction: 'vertical',
-      gutterSize: 4,
-    });
+    if(!this.readOnly) {
+      Split(['#semantics-frames-pane', '#semantics-schemata-pane'], {
+        sizes: [40, 60],
+        minSize: 400,
+        gutterSize: 4,
+        elementStyle: (dimension, size, gutterSize) => {
+          return {
+            'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
+          }
+        },
+      });
+      Split(['#semantics-unified-frame-pane', '#semantics-slowal-frames-pane', '#examples'], {
+        sizes: [40, 40, 20],
+        direction: 'vertical',
+        gutterSize: 4,
+      });
+    }
     if(this.unifiedFrameId) {
       this.loadFrame();
     }
@@ -865,37 +864,34 @@ Object.assign(LexicalUnitEdit, {
       <div align="center">
         <div align="left" style="display: table;">
           <div class="unifiedFrame mt-3" v-bind:data-frame_id="unified_frame.id" id="lexical-unit" v-html="unified_frame_title"></div>
-          <table v-if="unified_frame.id" id="unified-frame" class="m-0 table-borderless border border-secondary text-dark frame active">
+          <table v-if="unified_frame.id" id="unified-frame" class="m-0 table-borderless border border-secondary text-dark">
           <tbody>
             <tr>
               <th scope="row" class="py-2 px-1 text-secondary role-header">Role</th>
-              
-              <template v-for="argument in unified_frame_arguments">
-                  <td
-                    class="argument py-2 px-1 border-top border-left border-secondary role-column"
-                    :class="getArgumentCSS(argument)"
-                    @click="unifiedFrameArgumentSelected(argument)"
-                    @mouseover="unifiedFrameArgumentHovered(argument)"
-                    @mouseleave="unifiedFrameArgumentHovered(null)"
-                  >
-                    {{ argument.role_type }}
-                    
-                    <div
-                      v-if="argument.role"
-                    >
-                      [{{ argument.role.str }}]
-                    </div>
-                    <div v-else>
-                      <ul class="ul-role">
-                        <li v-for="proposed_role in argument.proposed_roles">
-                          {{ proposed_role.str }}
-                        </li>
-                      </ul>
-                    </div>
-                  </td>
-              </template>
-              
-              
+              <td
+                class="argument py-2 px-1 border-top border-left border-secondary role-column"
+                :class="argument == active_unified_frame_argument && 'active'"
+                v-for="argument in unified_frame_arguments"
+                :key="argument.id"
+                @click="unifiedFrameArgumentSelected(argument)"
+                @mouseover="unifiedFrameArgumentHovered(argument)"
+                @mouseleave="unifiedFrameArgumentHovered(null)"
+              >
+                {{ argument.role_type }}
+                
+                <div
+                  v-if="argument.role"
+                >
+                  [{{ argument.role.str }}]
+                </div>
+                <div v-else>
+                  <ul class="ul-role">
+                    <li v-for="proposed_role in argument.proposed_roles">
+                      {{ proposed_role.str }}
+                    </li>
+                  </ul>
+                </div>
+              </td>
             </tr>
             <tr>
               <th scope="row" class="py-0 px-1 text-secondary role-header">Selectional preferences</th>
@@ -1013,7 +1009,7 @@ Object.assign(LexicalUnitEdit, {
             </div>
           </div>
         </div>
-      <div class="col w-100 p-0 tab-pane overflow-auto" id="examples">
+      <div v-if="!readOnly" class="col w-100 p-0 tab-pane overflow-auto" id="examples">
             <table id="semantics-examples" class="table table-sm table-hover">
                 <thead>
                     <tr>
diff --git a/entries/static/entries/js/components/UnificationComponent.js b/entries/static/entries/js/components/UnificationComponent.js
index 5ee1ebb5f11c9d66e6b472521ca6691262da383c..79291be97c711867caa72bf2a9fe35fc1229a4e9 100644
--- a/entries/static/entries/js/components/UnificationComponent.js
+++ b/entries/static/entries/js/components/UnificationComponent.js
@@ -41,7 +41,6 @@ export default {
     this.unifiedFrameSelected(window.initialUnifiedFrameId);
     $('#entries-list').length && Split(['#entries-list', '#entry-display'], {
       sizes: [20, 80],
-      minSize: 300,
       gutterSize: 4,
       elementStyle: (dimension, size, gutterSize) => {
         return {
@@ -51,8 +50,8 @@ export default {
     });
   },
   template: `
-    <div id="entries-list" class="col h-100 w-100 px-0">
-        <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-auto">
+    <div id="entries-list" class="col h-100 w-100 px-0 overflow-hidden">
+        <div id="entries-list-div" class="col p-0 h-100 w-100 overflow-hidden">
             <unification-switchable-list
               :unificationEntriesListRefreshKey="unificationEntriesListRefreshKey"
               :initialLexicalUnitId="lexicalUnitId ? lexicalUnitId : initialLexicalUnitId"
@@ -62,7 +61,7 @@ export default {
             />
         </div>
     </div>
-    <div id="entry-display" class="col h-100 p-0">
+    <div id="entry-display" class="col h-100 p-0 overflow-hidden">
         <unification-right-pane 
           ref="unificationRightPane"
           :entryId="entryId" 
diff --git a/entries/static/entries/js/unification_entries_for_frames_list.js b/entries/static/entries/js/unification_entries_for_frames_list.js
index 8221e310bc12b1dc20c922363aeaa98bef06c0cd..0b0d90681498593a0ac40c471c413f7312d5ab10 100644
--- a/entries/static/entries/js/unification_entries_for_frames_list.js
+++ b/entries/static/entries/js/unification_entries_for_frames_list.js
@@ -1,7 +1,7 @@
 function setup_entries_for_frames_list(options) {
     const can_see_assignees = has_permission("users.view_assignment");
 
-    function is_assigned_to_user_renderer (data) {
+    function is_assigned_to_user_renderer (_1, _2, data) {
         return (
             data
             && data.lexical_units
@@ -18,7 +18,7 @@ function setup_entries_for_frames_list(options) {
         columns: [
             { data: 'lemma' },
             { data: 'POS' },
-            { render: data => (data && data.lexical_units && data.lexical_units.some(lu => lu.assignee_username !== null)) ? gettext("nie") : gettext("tak") },
+            { render: (_1, _2, data) => (data && data.lexical_units && data.lexical_units.some(lu => lu.assignee_username !== null)) ? gettext("nie") : gettext("tak") },
             can_see_assignees ? { data: 'assignee_username' } : { render: is_assigned_to_user_renderer },
         ],
         hidden_columns: can_see_assignees ? [2] : [2,3],
diff --git a/entries/static/entries/js/unification_entries_list.js b/entries/static/entries/js/unification_entries_list.js
index 24240f3893d2e37181ef191d808702e62ba1f5f9..0db5af2c5162e514f116086864a76359892a6650 100644
--- a/entries/static/entries/js/unification_entries_list.js
+++ b/entries/static/entries/js/unification_entries_list.js
@@ -1,7 +1,7 @@
 function setup_entries_list(options) {
     const can_see_assignees = has_permission("users.view_assignment");
 
-    function is_assigned_to_user_renderer (data) {
+    function is_assigned_to_user_renderer (_1, _2, data) {
         return (
             data
             && data.lexical_units
@@ -16,7 +16,7 @@ function setup_entries_list(options) {
         columns: [
             { data: 'lemma' },
             { data: 'POS' },
-            { render: data => (data && data.lexical_units && data.lexical_units.some(lu => lu.assignee_username !== null)) ? gettext("nie") : gettext("tak") },
+            { render: (_1, _2, data) => (data && data.lexical_units && data.lexical_units.some(lu => lu.assignee_username !== null)) ? gettext("nie") : gettext("tak") },
             can_see_assignees ? { data: 'assignee_username' } : { render: is_assigned_to_user_renderer },
         ],
         selectEntryId: options.selectEntryId,
diff --git a/entries/static/entries/js/unification_frames_list.js b/entries/static/entries/js/unification_frames_list.js
index 3efb6c61f579ee761e2c70f29bef6edf761b1e18..63b2fc91270f87bd007102d16cd2eb0ad865a1dc 100644
--- a/entries/static/entries/js/unification_frames_list.js
+++ b/entries/static/entries/js/unification_frames_list.js
@@ -1,7 +1,7 @@
 function setup_frames_list(options) {
     const can_see_assignees = has_permission("users.view_assignment");
 
-    function is_assigned_to_user_renderer (data) {
+    function is_assigned_to_user_renderer (_1, _2, data) {
         return (
             data
             && data.assignee_username === window.USER_USERNAME
diff --git a/entries/views.py b/entries/views.py
index d69fd47df7de846184a42605632d4446d63c52ad..2a2d2f5875a863e1a799ad69651fa1345644aa47 100644
--- a/entries/views.py
+++ b/entries/views.py
@@ -7,6 +7,7 @@ from itertools import chain, product
 
 import simplejson
 
+from django.contrib.auth.models import User
 from django.contrib.auth.decorators import login_required
 from django.db.models import Prefetch, Q
 from django.http import JsonResponse, QueryDict
@@ -24,6 +25,7 @@ from semantics.models import Frame, PredefinedSelectionalPreference, SelectivePr
 
 from common.decorators import ajax_required, ajax
 from unifier.models import UnifiedFrame
+from users.models import Assignment
 
 from .forms import (
     EntryForm,
@@ -49,6 +51,7 @@ MAX_LAST_VISITED = 10
 #def test(request):
 #    return render(request, 'test.html', {})
 
+@login_required
 def entries(request):
     # TODO make this automatic by subclassing/configuring session object
     if 'last_visited' not in request.session:
@@ -347,6 +350,7 @@ def get_scroller_params(POST_data):
 #from django.db.models import Count
 
 @ajax_required
+@login_required
 def get_entries(request):
     if request.method == 'POST':
         errors_dict = dict()
@@ -372,9 +376,13 @@ def get_entries(request):
         
         linked_ids = set()
         if request.session['show_linked_entries']:
-            entries_linked = Entry.objects.filter(subentries__schema_hooks__argument_connections__schema_connections__subentry__entry__in=entries).distinct().exclude(id__in=entries)
+            entries_linked = Entry.objects.filter(pk__in=(
+                Entry.objects
+                .filter(subentries__schema_hooks__argument_connections__schema_connections__subentry__entry__in=entries)
+                .exclude(id__in=entries)
+            )).distinct()
             entries = entries | entries_linked
-            linked_ids = set(e.id for e in entries_linked)
+            linked_ids = set(entries_linked.values_list('id', flat=True))
         
         first_index, last_index = scroller_params['start'], scroller_params['start'] + scroller_params['length']
         order_field, order_dir = scroller_params['order']
@@ -386,12 +394,39 @@ def get_entries(request):
             order_field = 'pos__tag'
         if order_dir == 'desc':
             order_field = '-' + order_field
-        entries = entries.order_by(order_field).only('id', 'name', 'status__key', 'pos__tag')
+
+        if restrict_to_user:
+            # pre-filtering entries for performance reasons
+            entries = entries.filter(lexical_units__frames__assignments__user=User.objects.get(username=restrict_to_user))
+        entries = (
+            entries
+            .order_by(order_field)
+            .select_related('status', 'pos')
+            .prefetch_related(Prefetch("assignments", to_attr="_assignments"))
+        )
         if with_lexical_units:
-            entries = entries.prefetch_related("lexical_units")
+            entries = entries.prefetch_related(
+                Prefetch(
+                    "lexical_units",
+                    LexicalUnit.objects.prefetch_related(
+                        Prefetch(
+                            "frames",
+                            Frame.objects
+                            .select_related("slowal_frame_2_unified_frame")
+                            .prefetch_related(Prefetch("assignments", to_attr="_assignments")),
+                            to_attr="_frames",
+                        )
+                    )
+                )
+            )
         status_names = STATUS()
         POS_names = POS()
-        entriesList = list(entries)
+
+        def iter_lexical_units(e):
+            for lu in e.lexical_units.all():
+                lu._frame = lu._frames[0] if lu._frames else None
+                yield lu
+
         result = {
             'draw' : scroller_params['draw'],
             'recordsTotal': total,
@@ -403,7 +438,7 @@ def get_entries(request):
                     'status'  : status_names[e.status.key],
                     'POS'     : POS_names[e.pos.tag],
                     'related' : e.id in linked_ids,
-                    'assignee_username': assignment.user.username if (assignment := e.assignments.first()) else None,
+                    'assignee_username': e._assignments[0].user.username if e._assignments else None,
                     **(
                         {
                             'lexical_units': [
@@ -411,16 +446,16 @@ def get_entries(request):
                                     'pk': lu.pk,
                                     'display': str(lu),
                                     'assignee_username': (
-                                        assignment.user.username if (frame := lu.frames.first()) and (assignment := frame.assignments.first()) else None
+                                        lu._frame._assignments[0].user.username if lu._frame and lu._frame._assignments else None
                                     ),
-                                    'status': frame.status if (frame := lu.frames.first()) else "",
-                                    'unified_frame_id': frame.slowal_frame_2_unified_frame.unified_frame_id if (frame := lu.frames.first()) and hasattr(frame, 'slowal_frame_2_unified_frame') else -1,
-                                } for lu in e.lexical_units.all()
+                                    'status': lu._frame.status if lu._frame else "",
+                                    'unified_frame_id': lu._frame.slowal_frame_2_unified_frame.unified_frame_id if lu._frame and hasattr(lu._frame, 'slowal_frame_2_unified_frame') else -1,
+                                } for lu in iter_lexical_units(e)
                             ]
                         }
                         if with_lexical_units else {}
                     ),
-                } for e in entriesList[first_index:last_index]
+                } for e in entries[first_index:last_index]
             ],
         }
 
diff --git a/phrase_expansions/views.py b/phrase_expansions/views.py
index 58162d367760d4c7f9722760db4cc38a4881682a..4821f6485c9048b516345fb7a88987936f96e9a1 100644
--- a/phrase_expansions/views.py
+++ b/phrase_expansions/views.py
@@ -1,7 +1,7 @@
 from collections import defaultdict
 
+from django.contrib.auth.decorators import login_required
 from django.shortcuts import render
-
 from django.utils.translation import gettext as _
 
 from phrase_expansions.models import PhraseExpansionType, PhraseExpansion
@@ -13,7 +13,8 @@ def EXPANSION_OPINION():
         'unc' : _('wÄ…tpliwe'),
         'cer' : _('pewne'),
     }
-    
+
+@login_required
 def phrase_expansions(request):
     expansions = defaultdict(list)
     expansions = [
diff --git a/unifier/views.py b/unifier/views.py
index beb6b499b58177486b27f896cbe2591ebd5d4b42..119cac2c73c8c73f689153465e8d274c0a421355 100644
--- a/unifier/views.py
+++ b/unifier/views.py
@@ -1,5 +1,6 @@
 import json
 import requests
+from django.contrib.auth.decorators import login_required
 from django.db import transaction
 from django.http import JsonResponse, HttpResponse
 from django.shortcuts import get_object_or_404
@@ -206,6 +207,7 @@ def get_unified_frame_json(unifiedFrame, request):
     return { 'unified_frame_id': unifiedFrame.id, 'unified_frame': unifiedFrame_dict, 'subentries': subentries, 'frames' : slowal_frames_dict, 'alternations' : alternations, 'realisation_phrases' : realisation_phrases, 'realisation_descriptions' : realisation_descriptions, 'examples' : examples, 'last_visited' : request.session['last_visited'] }
 
 @ajax_required
+@login_required
 def get_unified_frame(request):
     if request.method == 'POST':
         #TODO (*)