Repository: shapiromatron/hawc (language: python)

Issue:
style fixes
Fixes various style issues, most of which were caused by the new epa style sheet. 
Before and after:
- Restore background color for SET tables
![image](https://user-images.githubusercontent.com/42587248/200639131-307e0d3e-98e6-488a-bdab-0412c6a07824.png)
![image](https://user-images.githubusercontent.com/42587248/200639332-e890cbd7-2145-4e2c-ba0f-63e54b182cca.png)

- Remove bottom margin on li elements
![image](https://user-images.githubusercontent.com/42587248/200639839-dfb65da6-9019-4f61-927b-c3b5a00a6c76.png)
![image](https://user-images.githubusercontent.com/42587248/200639747-0cda3bdb-a195-4061-9723-ce0aeb88377c.png)

- Maintain original link color for visited links
![image](https://user-images.githubusercontent.com/42587248/200640240-e4a99eca-f7ae-4352-81e6-9411b79e2d9e.png)
![image](https://user-images.githubusercontent.com/42587248/200640296-df657d0a-e3c2-4fb3-9b1a-db9555c69c4c.png)

- Remove bottom margin for all p elements
![image](https://user-images.githubusercontent.com/42587248/200640546-c8e281e4-079b-494b-8cbe-5fcfb33d22aa.png)
![image](https://user-images.githubusercontent.com/42587248/200640504-9e3907dc-4db6-40be-baab-e8775f08804b.png)
- This centers breadcrumb text
![image](https://user-images.githubusercontent.com/42587248/200869318-5bcccd51-ae2f-4484-8ca3-5d203df5544b.png)
![image](https://user-images.githubusercontent.com/42587248/200869514-96fd4f2b-9a8a-4ec9-b5ee-603a02dad7e8.png)



- Increase min page size from 50vh to 95vh to fix a bug on new assessments/short pages where the action button dropdown was cropped
![image](https://user-images.githubusercontent.com/42587248/200640867-a63e34ae-c215-4e39-ae42-65b4f951674f.png)
![image](https://user-images.githubusercontent.com/42587248/200640952-2e86e46d-c134-4ea7-b634-a9940d27ecdd.png)

- Add margin to page bottom to ensure footer is properly separated from page info
![image](https://user-images.githubusercontent.com/42587248/200641364-8178fc1f-16a8-438f-96b2-edab5f3a30f0.png)
![image](https://user-images.githubusercontent.com/42587248/200641394-33687df3-bc60-42e4-8ebe-c09df4867ed6.png)

- Ensure all action button dropdown links are the same color when active (i.e. clicked)
![image](https://user-images.githubusercontent.com/42587248/200641825-849f111f-e93c-4a5a-9785-1be2e1d90904.png)
![image](https://user-images.githubusercontent.com/42587248/200641929-cd31329b-7560-4e73-8603-c5638ee2fd99.png)

- Restore previous size and style of submit/apply filter buttons to match cancel/reset buttons
![image](https://user-images.githubusercontent.com/42587248/200642501-279d5d2d-c1a3-4732-a966-28867549ce84.png)
![image](https://user-images.githubusercontent.com/42587248/200642311-40404f9b-c24b-4e85-ba04-b31ce13ee38e.png)

- Fix management dashboard header alignment
![image](https://user-images.githubusercontent.com/42587248/200642822-865f88a5-a0b9-4972-b61c-641243a38812.png)
![image](https://user-images.githubusercontent.com/42587248/200642629-b633ef17-6587-472f-a76a-351dd1ae5aeb.png)



Discussion / hints:

Fix patch:
diff --git a/client/hawc_client/literature.py b/client/hawc_client/literature.py
index c8b6c08438..f76b3dacfa 100644
--- a/client/hawc_client/literature.py
+++ b/client/hawc_client/literature.py
@@ -188,7 +188,21 @@ def references(self, assessment_id: int) -> pd.DataFrame:
         Returns:
             pd.DataFrame: References data
         """
-        url = f"{self.session.root_url}/lit/api/assessment/{assessment_id}/references-download/"
+        url = f"{self.session.root_url}/lit/api/assessment/{assessment_id}/reference-export/"
+        response_json = self.session.get(url).json()
+        return pd.DataFrame(response_json)
+
+    def reference_user_tags(self, assessment_id: int) -> pd.DataFrame:
+        """
+        Retrieves all user tag for all references for an assessment.
+
+        Args:
+            assessment_id (int): Assessment ID
+
+        Returns:
+            pd.DataFrame: References user tag data
+        """
+        url = f"{self.session.root_url}/lit/api/assessment/{assessment_id}/user-tag-export/"
         response_json = self.session.get(url).json()
         return pd.DataFrame(response_json)
 
diff --git a/client/r/rhawc/R/HawcClient.R b/client/r/rhawc/R/HawcClient.R
index 8e61f18c1a..377334b3cd 100644
--- a/client/r/rhawc/R/HawcClient.R
+++ b/client/r/rhawc/R/HawcClient.R
@@ -151,7 +151,7 @@ HawcClient = function(baseUrl){
       return(as_data_frame(response))
     },
     lit_references = function(assessment_id) {
-      url = glue::glue("{root_url}/lit/api/assessment/{assessment_id}/references-download/")
+      url = glue::glue("{root_url}/lit/api/assessment/{assessment_id}/reference-export/")
       response = get_(url)
       return(as_data_frame(response))
     },
diff --git a/frontend/.eslintignore b/frontend/.eslintignore
index 1a5b53524d..aae872fb6c 100644
--- a/frontend/.eslintignore
+++ b/frontend/.eslintignore
@@ -1,1 +1,2 @@
 shared/parsers/query.js
+uswds/node_modules
diff --git a/frontend/lit/NestedTag.js b/frontend/lit/NestedTag.js
index 8e2d038488..9f7c29a70a 100644
--- a/frontend/lit/NestedTag.js
+++ b/frontend/lit/NestedTag.js
@@ -1,5 +1,6 @@
 import React from "react";
 import ReactDOM from "react-dom";
+import {getReferenceTagListUrl} from "shared/utils/urls";
 
 import PaginatedReferenceList from "./components/PaginatedReferenceList";
 
@@ -61,6 +62,10 @@ class NestedTag {
         };
         ReactDOM.render(<PaginatedReferenceList settings={settings} canEdit={canEdit} />, el);
     }
+
+    get_list_link() {
+        return getReferenceTagListUrl(this.assessment_id, this.data.pk);
+    }
 }
 
 export default NestedTag;
diff --git a/frontend/lit/Reference.js b/frontend/lit/Reference.js
index 9a8332e165..4d5ea0b597 100644
--- a/frontend/lit/Reference.js
+++ b/frontend/lit/Reference.js
@@ -8,6 +8,7 @@ class Reference {
         this.data = data;
         this._quickSearchText = `${data.title}-${data.year}-${data.authors}-${data.authors_short}`.toLowerCase();
         this.tags = data.tags.map(tagId => tagtree.dict[tagId]);
+        this.userTags = data.user_tags ? data.user_tags.map(tagId => tagtree.dict[tagId]) : null;
     }
 
     static get_detail_url(id, subtype) {
diff --git a/frontend/lit/ReferenceTreeBrowse/Main.js b/frontend/lit/ReferenceTreeBrowse/Main.js
index 74876e0370..bbd1745e61 100644
--- a/frontend/lit/ReferenceTreeBrowse/Main.js
+++ b/frontend/lit/ReferenceTreeBrowse/Main.js
@@ -72,15 +72,17 @@ class ReferenceTreeMain extends Component {
                     ) : store.selectedTag === null ? (
                         <h4>Available references</h4>
                     ) : (
-                        <h4>
-                            <span>
-                                {selectedReferences && selectedReferences.length > 0
-                                    ? `${filteredReferences.length} references tagged:`
-                                    : "References tagged:"}
-                            </span>
+                        <div className="d-flex align-items-center">
+                            <h4>
+                                <span>
+                                    {selectedReferences && selectedReferences.length > 0
+                                        ? `${filteredReferences.length} references tagged:`
+                                        : "References tagged:"}
+                                </span>
+                            </h4>
                             <span className="ml-2 refTag">{store.selectedTag.get_full_name()}</span>
                             <span>{yearText}</span>
-                        </h4>
+                        </div>
                     )}
                 </div>
                 <div className="col-md-3">
@@ -124,6 +126,7 @@ class ReferenceTreeMain extends Component {
                         selectedTag={store.selectedTag}
                         untaggedHandleClick={store.handleUntaggedReferenceClick}
                         untaggedCount={store.config.untaggedReferenceCount}
+                        untaggedReferencesSelected={store.untaggedReferencesSelected}
                     />
                 </div>
             </div>
diff --git a/frontend/lit/TagReferences/Main.js b/frontend/lit/TagReferences/Main.js
index 9b5ae36165..65682eade0 100644
--- a/frontend/lit/TagReferences/Main.js
+++ b/frontend/lit/TagReferences/Main.js
@@ -2,168 +2,283 @@ import {toJS} from "mobx";
 import {inject, observer} from "mobx-react";
 import PropTypes from "prop-types";
 import React, {Component} from "react";
+import Alert from "shared/components/Alert";
+import HelpTextPopup from "shared/components/HelpTextPopup";
+import Modal from "shared/components/Modal";
+import {LocalStorageBoolean} from "shared/utils/LocalStorage";
 
 import Reference from "../components/Reference";
-import ReferenceSortSelector from "../components/ReferenceSortSelector";
 import TagTree from "../components/TagTree";
 
+@inject("store")
+@observer
+class ReferenceListItem extends Component {
+    render() {
+        const {store, reference, selectedReferencePk} = this.props,
+            isSelected = reference.data.pk === selectedReferencePk,
+            divClass = `d-flex justify-content-between align-items-center reference ${
+                isSelected ? "selected" : ""
+            }`,
+            title = store.config.conflict_resolution ? "has consensus tag(s)" : "tagged";
+
+        return (
+            <div className={divClass} onClick={() => store.setReference(reference)}>
+                <p className="mb-0 pr-1">{reference.shortCitation()}</p>
+                <div className="mr-1 d-flex" style={{flexWrap: "nowrap"}}>
+                    {store.config.conflict_resolution && reference.userTags ? (
+                        <i
+                            className="fa fa-fw fa-tags small-tag-icon user"
+                            title={"has tags from you"}
+                            aria-hidden="true"></i>
+                    ) : null}
+                    {reference.tags.length > 0 ? (
+                        <i
+                            className="fa fa-fw fa-tags small-tag-icon consensus mr-1"
+                            title={title}
+                            aria-hidden="true"></i>
+                    ) : null}
+                </div>
+            </div>
+        );
+    }
+}
+ReferenceListItem.propTypes = {
+    reference: PropTypes.object.isRequired,
+    selectedReferencePk: PropTypes.number.isRequired,
+    store: PropTypes.object,
+};
+
 @inject("store")
 @observer
 class TagReferencesMain extends Component {
     constructor(props) {
         super(props);
-        this.savedPopup = React.createRef();
-    }
-    _setSaveIndicator() {
-        const el = this.savedPopup.current;
-        if (el) {
-            this.props.store.setSaveIndicatorElement(el);
-        }
-    }
-    componentDidMount() {
-        this._setSaveIndicator();
-    }
-    componentDidUpdate() {
-        this._setSaveIndicator();
+        this.showFullTag = new LocalStorageBoolean("lit-showFullTag", true);
+        this.pinInstructions = new LocalStorageBoolean("lit-pinInstructions", false);
+        this.state = {
+            showFullTag: this.showFullTag.value,
+            pinInstructions: this.pinInstructions.value,
+        };
     }
     render() {
         const {store} = this.props,
-            selectedReferencePk = store.selectedReference ? store.selectedReference.data.pk : null,
-            selectedReferenceTags = store.selectedReferenceTags ? store.selectedReferenceTags : [],
-            allTagged = store.referencesUntagged.length == 0,
-            anyTagged = store.referencesTagged.length > 0,
-            anyUntagged = store.referencesUntagged.length > 0;
+            {hasReference, reference, referenceTags, referenceUserTags} = store,
+            selectedReferencePk = hasReference ? reference.data.pk : -1; // -1 will never match
+
         return (
             <div className="row">
-                <div className="col-md-3">
-                    <ReferenceSortSelector onChange={store.sortReferences} />
-                    <h4>References</h4>
-                    <div className="card">
-                        <div className="card-header p-2">
-                            <button
-                                className="btn btn-link"
-                                data-toggle="collapse"
-                                data-target="#taggedRefList">
-                                Tagged
-                            </button>
-                        </div>
-                        <div
-                            id="taggedRefList"
-                            className="show card-body ref-container px-1 resize-y"
-                            style={{minHeight: "10vh", height: anyTagged ? "40vh" : "10vh"}}>
-                            {store.referencesTagged.map(ref => (
-                                <p
-                                    key={ref.data.pk}
-                                    className={
-                                        ref.data.pk === selectedReferencePk
-                                            ? "reference selected"
-                                            : "reference"
-                                    }
-                                    onClick={() => store.changeSelectedReference(ref)}>
-                                    {ref.shortCitation()}
-                                </p>
-                            ))}
-                        </div>
+                <div className={store.filterClass} id="refFilter">
+                    <div
+                        className="row px-3 mb-2 justify-content-between"
+                        style={{maxHeight: "1.9rem"}}>
+                        <h4>References</h4>
                     </div>
-                    <div className="card mt-3">
-                        <div className="card-header p-2">
-                            <button
-                                className="btn btn-link"
-                                data-toggle="collapse"
-                                data-target="#untaggedRefList">
-                                Untagged
-                            </button>
-                        </div>
+                    <div className="card">
                         <div
-                            id="untaggedRefList"
-                            className="show card-body px-1 resize-y"
-                            style={{minHeight: "10vh", height: anyUntagged ? "40vh" : "10vh"}}>
-                            {store.referencesUntagged.map(ref => (
-                                <p
-                                    key={ref.data.pk}
-                                    className={
-                                        ref.data.pk === selectedReferencePk
-                                            ? "reference selected"
-                                            : "reference"
-                                    }
-                                    onClick={() => store.changeSelectedReference(ref)}>
-                                    {ref.shortCitation()}
-                                </p>
-                            ))}
+                            id="fullRefList"
+                            className="show card-body ref-container px-0 py-1 resize-y"
+                            style={{height: "50rem"}}>
+                            {store.references.map(reference => {
+                                return (
+                                    <ReferenceListItem
+                                        key={reference.data.pk}
+                                        reference={reference}
+                                        selectedReferencePk={selectedReferencePk}
+                                    />
+                                );
+                            })}
                         </div>
                     </div>
                 </div>
-                <div className="col-md-6">
-                    {store.selectedReference ? (
+                <div className={store.filterClass} id="taggingCol">
+                    {store.hasReference ? (
                         <div>
-                            <h4>Tags for current reference</h4>
-                            <div className="well" style={{minHeight: "50px"}}>
-                                {selectedReferenceTags.map((tag, i) => (
-                                    <span
-                                        key={i}
-                                        title="click to remove"
-                                        className="refTag refTagEditing"
-                                        onClick={() => store.removeTag(tag)}>
-                                        {tag.get_full_name()}
-                                    </span>
-                                ))}
-                            </div>
-                            {store.errorOnSave ? (
-                                <div className="alert alert-danger">
-                                    An error occurred in saving; please wait a moment and retry. If
-                                    the error persists please contact HAWC staff.
-                                </div>
-                            ) : null}
-                            <div style={{paddingBottom: "1em"}}>
+                            <div className="d-flex justify-content-between">
+                                <h4 className="my-0">
+                                    <button
+                                        id="collapse-btn"
+                                        className="btn btn-sm btn-info"
+                                        title="Show/hide reference sidebar"
+                                        type="button"
+                                        onClick={() => store.toggleSlideAway()}>
+                                        <div className={store.filterClass} id="filter-btn">
+                                            <i
+                                                id="caret-left"
+                                                className="fa fa-caret-left "
+                                                aria-hidden="true"></i>
+                                            &nbsp;
+                                            <i className="fa fa-list-ul" aria-hidden="true"></i>
+                                            &nbsp;
+                                        </div>
+                                    </button>
+                                    &nbsp; Currently Applied Tags
+                                    <HelpTextPopup
+                                        title={""}
+                                        content={"Click on a tag to remove"}
+                                    />
+                                </h4>
                                 <button
-                                    className="btn btn-primary"
+                                    className="btn btn-primary pt-1"
+                                    title="Save and go to next reference"
                                     onClick={() => store.saveAndNext()}>
-                                    Save and go to next untagged
+                                    <i className="fa fa-save"></i>&nbsp;Save and next
                                 </button>
-                                <span>&nbsp;</span>
-                                <button
-                                    className="btn btn-light"
-                                    onClick={() => store.removeAllTags()}>
-                                    Remove all tags
-                                </button>
-                                <span
-                                    ref={this.savedPopup}
-                                    className="btn litSavedIcon"
-                                    style={{display: "none"}}>
-                                    Saved!
-                                </span>
-                                <a
-                                    className="btn btn-light float-right"
-                                    rel="noopener noreferrer"
-                                    target="_blank"
-                                    href={store.selectedReference.get_edit_url()}
-                                    title="Cleanup imported reference details">
-                                    Edit
-                                </a>
+                            </div>
+                            {store.errorOnSave ? (
+                                <Alert
+                                    className="alert-danger mt-2"
+                                    message="An error occurred in saving; please wait a moment and retry. If the error persists please contact HAWC staff."
+                                />
+                            ) : null}
+                            <div className="well" style={{minHeight: "50px"}}>
+                                {store.successMessage ? (
+                                    <Alert
+                                        className="alert-success fade-in-out"
+                                        icon="fa-check-square"
+                                        message={store.successMessage}
+                                    />
+                                ) : (
+                                    <>
+                                        {referenceTags.map((tag, i) => (
+                                            <span
+                                                key={i}
+                                                title={
+                                                    store.config.conflict_resolution
+                                                        ? "Tag: ".concat(tag.get_full_name())
+                                                        : tag.get_full_name()
+                                                }
+                                                className={
+                                                    store.hasTag(referenceUserTags, tag)
+                                                        ? "refTag cursor-pointer"
+                                                        : "refTag refUserTagRemove cursor-pointer"
+                                                }
+                                                onClick={() => store.toggleTag(tag)}>
+                                                {this.state.showFullTag
+                                                    ? tag.get_full_name()
+                                                    : tag.data.name}
+                                            </span>
+                                        ))}
+                                        {referenceUserTags
+                                            .filter(tag => !store.hasTag(referenceTags, tag))
+                                            .map((tag, i) => (
+                                                <span
+                                                    key={i}
+                                                    title={"Proposed: ".concat(tag.get_full_name())}
+                                                    className="refTag refUserTag cursor-pointer"
+                                                    onClick={() => store.removeTag(tag)}>
+                                                    {this.state.showFullTag
+                                                        ? tag.get_full_name()
+                                                        : tag.data.name}
+                                                </span>
+                                            ))}
+                                    </>
+                                )}
                             </div>
                             <Reference
-                                reference={store.selectedReference}
+                                reference={reference}
+                                keywordDict={store.config.keywords}
                                 showActions={false}
                                 showHr={false}
                                 showTags={false}
+                                showActionsTagless={true}
+                                actionsBtnClassName={"btn-sm btn-secondary"}
+                                extraActions={[
+                                    <div
+                                        className="dropdown-item cursor-pointer"
+                                        key={4}
+                                        onClick={() => store.removeAllTags()}>
+                                        &nbsp;Remove all tags
+                                    </div>,
+                                    <div
+                                        className="dropdown-item cursor-pointer"
+                                        key={5}
+                                        onClick={() => {
+                                            this.showFullTag.toggle();
+                                            this.setState({showFullTag: this.showFullTag.value});
+                                        }}>
+                                        &nbsp;
+                                        {this.state.showFullTag
+                                            ? "Show collapsed tag"
+                                            : "Show full tag"}
+                                    </div>,
+                                    store.config.instructions.length > 0 ? (
+                                        <div
+                                            className="dropdown-item cursor-pointer"
+                                            key={6}
+                                            onClick={() => store.setInstructionsModal(true)}>
+                                            &nbsp;View instructions
+                                        </div>
+                                    ) : null,
+                                ]}
                             />
                         </div>
                     ) : (
                         <h4>Select a reference</h4>
                     )}
-                    {selectedReferencePk === null && allTagged ? (
-                        <div className="alert alert-success">
-                            All references have been successfully tagged. Congratulations!
+                    {selectedReferencePk === -1 ? (
+                        <div className="alert alert-danger">No references found.</div>
+                    ) : null}
+                    {this.state.pinInstructions && store.config.instructions.length > 0 ? (
+                        <div
+                            className="alert alert-info mt-3 resize-y"
+                            style={
+                                store.config.instructions.length > 1000 ? {height: "30vh"} : null
+                            }>
+                            <b>Screening Instructions:</b>
+                            <button
+                                type="button"
+                                className="close"
+                                onClick={() => {
+                                    this.pinInstructions.set(false);
+                                    this.setState({pinInstructions: false});
+                                }}>
+                                &times;
+                            </button>
+                            <div dangerouslySetInnerHTML={{__html: store.config.instructions}} />
                         </div>
                     ) : null}
                 </div>
-                <div className="col-md-3">
+                <div className="px-3 w-25">
                     <h4>Available tags</h4>
                     <TagTree
                         tagtree={toJS(store.tagtree)}
                         handleTagClick={tag => store.addTag(tag)}
+                        showTagHoverAdd={true}
                     />
                 </div>
+                <Modal
+                    isShown={store.showInstructionsModal && store.config.instructions.length > 0}
+                    onClosed={() => store.setInstructionsModal(false)}>
+                    <div className="modal-header pb-0">
+                        <h4>
+                            Screening Instructions
+                            <button
+                                type="button"
+                                title={
+                                    this.state.pinInstructions ? "Unpin from page" : "Pin to page"
+                                }
+                                className="btn btn-sm btn-info ml-3"
+                                onClick={() => {
+                                    this.pinInstructions.toggle();
+                                    this.setState({pinInstructions: this.pinInstructions.value});
+                                    store.setInstructionsModal(false);
+                                }}>
+                                <i className="fa fa-thumb-tack" aria-hidden="true"></i>
+                            </button>
+                        </h4>
+                        <button
+                            type="button"
+                            className="float-right close"
+                            onClick={() => store.setInstructionsModal(false)}
+                            aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <div
+                        className="modal-body"
+                        dangerouslySetInnerHTML={{__html: store.config.instructions}}
+                    />
+                </Modal>
             </div>
         );
     }
diff --git a/frontend/lit/TagReferences/store.js b/frontend/lit/TagReferences/store.js
index 775dd53e42..2562f245ae 100644
--- a/frontend/lit/TagReferences/store.js
+++ b/frontend/lit/TagReferences/store.js
@@ -1,102 +1,109 @@
 import _ from "lodash";
 import {action, computed, observable, toJS} from "mobx";
+import h from "shared/utils/helpers";
 
-import $ from "$";
-
-import {sortReferences} from "../constants";
 import Reference from "../Reference";
 import TagTree from "../TagTree";
 
 class Store {
     config = null;
-    saveIndicatorElement = null;
     @observable tagtree = null;
     @observable references = [];
-    @observable selectedReference = null;
-    @observable selectedReferenceTags = null;
+    @observable reference = null;
+    @observable referenceTags = null;
+    @observable referenceUserTags = null;
     @observable errorOnSave = false;
+    @observable successMessage = "";
+    @observable filterClass = "";
+    @observable showInstructionsModal = false;
 
     constructor(config) {
         this.config = config;
         this.tagtree = new TagTree(config.tags[0]);
-        this.references = Reference.array(config.refs, this.tagtree);
+        this.references = Reference.array(config.refs, this.tagtree, false);
         // set first reference
-        if (this.referencesUntagged.length > 0) {
-            this.changeSelectedReference(this.referencesUntagged[0]);
-        } else if (this.references.length === 1) {
-            this.changeSelectedReference(this.references[0]);
+        if (this.references.length > 0) {
+            this.setReference(this.references[0]);
         }
     }
-
-    @action.bound changeSelectedReference(reference) {
-        this.selectedReference = reference;
-        this.selectedReferenceTags = reference.tags.slice(0); // shallow copy
+    @computed get hasReference() {
+        return this.reference !== null;
+    }
+    @action.bound setReference(reference) {
+        this.reference = reference;
+        this.referenceTags = reference.tags.slice(0); // shallow copy
+        this.referenceUserTags = reference.userTags
+            ? reference.userTags.slice(0)
+            : reference.tags.slice(0);
+    }
+    hasTag(tags, tag) {
+        return !!_.find(tags, e => e.data.pk == tag.data.pk);
     }
     @action.bound addTag(tag) {
         if (
-            this.selectedReference &&
-            !_.find(this.selectedReferenceTags, el => el.data.pk === tag.data.pk)
+            this.hasReference &&
+            !_.find(this.referenceUserTags, el => el.data.pk === tag.data.pk)
         ) {
-            this.selectedReferenceTags.push(tag);
+            this.referenceUserTags.push(tag);
         }
     }
     @action.bound removeTag(tag) {
-        _.remove(this.selectedReferenceTags, el => el.data.pk === tag.data.pk);
+        _.remove(this.referenceUserTags, el => el.data.pk === tag.data.pk);
+    }
+    @action.bound toggleTag(tag) {
+        return this.hasTag(this.referenceUserTags, tag) ? this.removeTag(tag) : this.addTag(tag);
+    }
+    @action.bound handleSaveSuccess(response) {
+        const {resolved} = response;
+        this.errorOnSave = false;
+        this.reference.userTags = resolved ? null : toJS(this.referenceUserTags);
+        if (!this.config.conflict_resolution || resolved) {
+            this.reference.tags = toJS(this.referenceUserTags);
+        }
+        window.setTimeout(
+            action(() => {
+                const index = _.findIndex(
+                        this.references,
+                        ref => ref.data.pk === this.reference.data.pk
+                    ),
+                    nextIndex = this.references.length > index + 1 ? index + 1 : 0,
+                    reference = this.references[nextIndex];
+                this.successMessage = "";
+                this.setReference(reference);
+            }),
+            1000
+        );
+        this.successMessage = resolved ? "Saved! Tags added with no conflict." : "Saved!";
+    }
+    @action.bound handleSaveFailure() {
+        this.errorOnSave = true;
     }
     @action.bound saveAndNext() {
+        this.successMessage = "";
+        this.errorOnSave = false;
         const payload = {
-                pk: this.selectedReference.data.pk,
-                tags: this.selectedReferenceTags.map(tag => tag.data.pk),
+                pk: this.reference.data.pk,
+                tags: this.referenceUserTags.map(tag => tag.data.pk),
             },
-            success = () => {
-                const $el = $(this.saveIndicatorElement),
-                    index = _.findIndex(
-                        this.references,
-                        ref => ref.data.pk === this.selectedReference.data.pk
-                    );
-
-                if ($el.length !== 1) {
-                    throw "`this.saveIndicatorElement` not found.";
-                }
-
-                this.errorOnSave = false;
-                $el.fadeIn().fadeOut({
-                    complete: () => {
-                        this.selectedReference.tags = toJS(this.selectedReferenceTags);
-                        this.references.splice(index, 1, toJS(this.selectedReference));
-                        this.selectedReference = null;
-                        this.selectedReferenceTags = null;
-                        if (this.referencesUntagged.length > 0) {
-                            this.changeSelectedReference(this.referencesUntagged[0]);
-                        }
-                    },
-                });
-            },
-            failure = data => {
-                console.error(data);
-                this.errorOnSave = true;
-            };
-
-        $.post(`/lit/api/reference/${this.selectedReference.data.pk}/tag/`, payload, v =>
-            v.status === "success" ? success() : failure()
-        ).fail(failure);
+            url = `/lit/api/reference/${this.reference.data.pk}/tag/`;
+        h.handleSubmit(
+            url,
+            "POST",
+            this.config.csrf,
+            payload,
+            this.handleSaveSuccess,
+            this.handleSaveFailure,
+            this.handleSaveFailure
+        );
     }
     @action.bound removeAllTags() {
-        this.selectedReferenceTags = [];
+        this.referenceUserTags = [];
     }
-    @action.bound setSaveIndicatorElement(el) {
-        this.saveIndicatorElement = el;
-    }
-
-    @action.bound sortReferences(sortBy) {
-        this.references = sortReferences(this.references, sortBy);
-    }
-
-    @computed get referencesTagged() {
-        return this.references.filter(ref => ref.tags.length > 0);
+    @action.bound toggleSlideAway() {
+        this.filterClass = this.filterClass == "" ? "slideAway" : "";
     }
-    @computed get referencesUntagged() {
-        return this.references.filter(ref => ref.tags.length === 0);
+    @action.bound setInstructionsModal(input) {
+        this.showInstructionsModal = input;
     }
 }
 
diff --git a/frontend/lit/components/Reference.js b/frontend/lit/components/Reference.js
index 59dcc2cf04..a2e7cd77c9 100644
--- a/frontend/lit/components/Reference.js
+++ b/frontend/lit/components/Reference.js
@@ -8,6 +8,33 @@ import {getReferenceTagListUrl} from "shared/utils/urls";
 
 import ReferenceButton from "./ReferenceButton";
 
+const markKeywords = (text, allSettings) => {
+        const all_tokens = allSettings.set1.keywords
+                .concat(allSettings.set2.keywords)
+                .concat(allSettings.set3.keywords),
+            all_re = new RegExp(all_tokens.join("|"), "gim");
+
+        if (all_tokens.length === 0) {
+            return text;
+        }
+        text = text.replace(all_re, match => `<mark>${match}</mark>`);
+        text = markText(text, allSettings.set1);
+        text = markText(text, allSettings.set2);
+        text = markText(text, allSettings.set3);
+        return text;
+    },
+    markText = (text, settings) => {
+        if (settings.keywords.length === 0) {
+            return text;
+        }
+        const re = new RegExp(`<mark>(?<token>${settings.keywords.join("|")})</mark>`, "gim");
+        return text.replace(
+            re,
+            (match, token) =>
+                `<mark class="hawc-mk" title="${settings.name}" style='border-bottom: 1px solid ${settings.color}; box-shadow: inset 0 -4px 0 ${settings.color};'>${token}</mark>`
+        );
+    };
+
 class Reference extends Component {
     renderIdentifiers(data) {
         const nodes = [];
@@ -70,21 +97,37 @@ class Reference extends Component {
     }
 
     render() {
-        const {reference, showActions, showTags, showHr, actionsBtnClassName} = this.props,
+        const {
+                reference,
+                showActions,
+                showTags,
+                showHr,
+                showActionsTagless,
+                actionsBtnClassName,
+                extraActions,
+                keywordDict,
+            } = this.props,
             {data, tags} = reference,
             authors = data.authors || data.authors_short || reference.NO_AUTHORS_TEXT,
             year = data.year || "",
             actionItems = [
-                <ActionLink key={0} label="Edit tags" href={data.editTagUrl} />,
+                <ActionLink key={0} label="Edit reference tags" href={data.editTagUrl} />,
                 <ActionLink key={1} label="Edit reference" href={data.editReferenceUrl} />,
                 <ActionLink key={2} label="Delete reference" href={data.deleteReferenceUrl} />,
-            ];
+                <ActionLink key={3} label="Tag status" href={data.tagStatusUrl} />,
+            ].concat(extraActions);
 
         return (
-            <div className="referenceDetail">
+            <div className="referenceDetail pb-2">
                 <div className="sticky-offset-anchor" id={`referenceId${data.pk}`}></div>
                 {
                     <div className="ref_small">
+                        {showActionsTagless ? (
+                            <ActionsButton
+                                dropdownClasses={actionsBtnClassName}
+                                items={actionItems.slice(1)}
+                            />
+                        ) : null}
                         <span>
                             {authors}&nbsp;{year}
                         </span>
@@ -96,18 +139,38 @@ class Reference extends Component {
                         ) : null}
                     </div>
                 }
-                {data.title ? <p className="ref_title">{data.title}</p> : null}
+                {data.title ? (
+                    keywordDict ? (
+                        <p
+                            className="ref_title py-1"
+                            dangerouslySetInnerHTML={{
+                                __html: markKeywords(data.title, keywordDict),
+                            }}
+                        />
+                    ) : (
+                        <p className="ref_title py-1">{data.title}</p>
+                    )
+                ) : null}
                 {data.journal ? <p className="ref_small">{data.journal}</p> : null}
                 {data.abstract ? (
-                    <div className="abstracts" dangerouslySetInnerHTML={{__html: data.abstract}} />
+                    <div
+                        className="abstracts resize-y p-2"
+                        style={data.abstract.length > 1500 ? {height: "45vh"} : null}
+                        dangerouslySetInnerHTML={
+                            keywordDict
+                                ? {__html: markKeywords(data.abstract, keywordDict)}
+                                : {__html: data.abstract}
+                        }
+                    />
                 ) : null}
                 {showTags && tags.length > 0 ? (
                     <p>
                         {tags.map((tag, i) => (
                             <a
+                                style={{color: "white"}}
                                 key={i}
                                 href={getReferenceTagListUrl(data.assessment_id, tag.data.pk)}
-                                className="referenceTag badge badge-info mr-1">
+                                className="refTag mt-1">
                                 {tag.get_full_name()}
                             </a>
                         ))}
@@ -142,6 +205,9 @@ Reference.propTypes = {
     actionsBtnClassName: PropTypes.string,
     showHr: PropTypes.bool,
     showTags: PropTypes.bool,
+    showActionsTagless: PropTypes.bool,
+    extraActions: PropTypes.arrayOf(PropTypes.element),
+    keywordDict: PropTypes.object,
 };
 
 Reference.defaultProps = {
@@ -149,6 +215,9 @@ Reference.defaultProps = {
     actionsBtnClassName: "btn-sm",
     showHr: false,
     showTags: true,
+    showActionsTagsless: false,
+    extraActions: null,
+    keywordDict: null,
 };
 
 export default Reference;
diff --git a/frontend/lit/components/TagActions.js b/frontend/lit/components/TagActions.js
index e20952d645..58d8cd6907 100644
--- a/frontend/lit/components/TagActions.js
+++ b/frontend/lit/components/TagActions.js
@@ -9,7 +9,7 @@ const getActionLinks = function(assessmentId, tagId, untagged, canEdit) {
             <ActionLink
                 key={0}
                 label="Tag untagged references"
-                href={`/lit/assessment/${assessmentId}/tag/untagged/`}
+                href={`/lit/assessment/${assessmentId}/tag/?untagged=on`}
             />
         );
     }
@@ -18,12 +18,12 @@ const getActionLinks = function(assessmentId, tagId, untagged, canEdit) {
             <ActionLink
                 key={1}
                 label="Download references"
-                href={`/lit/api/tags/${tagId}/references/?format=xlsx`}
+                href={`/lit/api/assessment/${assessmentId}/reference-export/?tag=${tagId}&include_descendants=on&format=xlsx`}
             />,
             <ActionLink
                 key={2}
                 label="Download references (table-builder format)"
-                href={`/lit/api/tags/${tagId}/references/?format=xlsx&exporter=table-builder`}
+                href={`/lit/api/assessment/${assessmentId}/reference-export/?tag=${tagId}&include_descendants=on&format=xlsx&export_format=table-builder`}
             />,
         ];
         if (canEdit) {
@@ -31,7 +31,7 @@ const getActionLinks = function(assessmentId, tagId, untagged, canEdit) {
                 <ActionLink
                     key={3}
                     label="Tag references with this tag (but not descendants)"
-                    href={`/lit/tag/${tagId}/tag/`}
+                    href={`/lit/assessment/${assessmentId}/tag/?tags=${tagId}`}
                 />
             );
         }
diff --git a/frontend/lit/components/TagTree.js b/frontend/lit/components/TagTree.js
index c3d4ffd37e..b4f0185573 100644
--- a/frontend/lit/components/TagTree.js
+++ b/frontend/lit/components/TagTree.js
@@ -13,8 +13,7 @@ class TagNode extends Component {
         };
     }
     render() {
-        const {tag, showReferenceCount, handleOnClick, selectedTag} = this.props,
-            tagClass = tag === selectedTag ? "d-flex nestedTag selected" : "d-flex nestedTag",
+        const {tag, showReferenceCount, handleOnClick, selectedTag, showTagHoverAdd} = this.props,
             hasChildren = tag.children.length > 0,
             expanderIcon = this.state.expanded ? "fa-minus" : "fa-plus",
             toggleExpander = e => {
@@ -26,21 +25,35 @@ class TagNode extends Component {
 
         return (
             <>
-                <div className={tagClass} onClick={() => handleOnClick(tag)}>
-                    <div style={{width: (tag.depth - 1) * 10 + 25}}>
+                <div
+                    className={"d-flex nestedTag align-items-center"}
+                    onClick={() => handleOnClick(tag)}>
+                    <div
+                        className="d-flex justify-content-end"
+                        style={{width: (tag.depth - 1) * 10 + 25}}>
                         {hasChildren ? (
                             <button
-                                className="float-right btn btn-sm px-2"
+                                className="d-flex btn btn-sm px-1"
                                 onClick={toggleExpander}
                                 type="button">
-                                <i className={`fa ${expanderIcon}`}></i>
+                                <i
+                                    className={`fa ${expanderIcon}`}
+                                    style={{fontSize: "0.8rem"}}></i>
                             </button>
                         ) : null}
                     </div>
-                    <div style={{flex: 1}}>
+                    <div
+                        className={(showTagHoverAdd ? "tagHoverAdd" : "tagHover").concat(
+                            tag === selectedTag ? " tagSelected" : ""
+                        )}
+                        style={{flex: 1}}>
                         <span>
                             {tag.data.name}
-                            {showReferenceCount ? ` (${tag.get_references_deep().length})` : null}
+                            {showReferenceCount ? (
+                                <span className="ml-2 badge badge-dark">
+                                    {tag.get_references_deep().length}
+                                </span>
+                            ) : null}
                         </span>
                     </div>
                 </div>
@@ -52,6 +65,7 @@ class TagNode extends Component {
                               handleOnClick={handleOnClick}
                               showReferenceCount={showReferenceCount}
                               selectedTag={selectedTag}
+                              showTagHoverAdd={showTagHoverAdd}
                           />
                       ))
                     : null}
@@ -64,6 +78,7 @@ TagNode.propTypes = {
     handleOnClick: PropTypes.func.isRequired,
     showReferenceCount: PropTypes.bool.isRequired,
     selectedTag: PropTypes.object,
+    showTagHoverAdd: PropTypes.bool,
 };
 
 @observer
@@ -76,9 +91,12 @@ class TagTree extends Component {
             selectedTag,
             untaggedHandleClick,
             untaggedCount,
+            showTagHoverAdd,
+            untaggedReferencesSelected,
+            style,
         } = this.props;
         return (
-            <div className="resize-y" style={{maxHeight: "80vh"}}>
+            <div id="litTagtree" className="resize-y p-2 mt-2" style={style}>
                 {tagtree.rootNode.children.map((tag, i) => (
                     <TagNode
                         key={i}
@@ -86,11 +104,17 @@ class TagTree extends Component {
                         handleOnClick={handleTagClick}
                         showReferenceCount={showReferenceCount}
                         selectedTag={selectedTag}
+                        showTagHoverAdd={showTagHoverAdd}
                     />
                 ))}
                 {untaggedHandleClick ? (
-                    <p className="nestedTag mt-2" onClick={untaggedHandleClick}>
-                        Untagged References: ({untaggedCount})
+                    <p
+                        className={`nestedTag mt-2 tagHover ${
+                            untaggedReferencesSelected ? "tagSelected" : ""
+                        }`}
+                        onClick={untaggedHandleClick}>
+                        Untagged References:
+                        <span className="ml-2 badge badge-dark">{untaggedCount}</span>
                     </p>
                 ) : null}
             </div>
@@ -104,10 +128,16 @@ TagTree.propTypes = {
     selectedTag: PropTypes.object,
     untaggedCount: PropTypes.number,
     untaggedHandleClick: PropTypes.func,
+    showTagHoverAdd: PropTypes.bool,
+    untaggedReferencesSelected: PropTypes.bool,
+    style: PropTypes.object,
 };
 TagTree.defaultProps = {
     showReferenceCount: false,
     handleTagClick: h.noop,
+    showTagHoverAdd: false,
+    untaggedReferencesSelected: false,
+    style: {},
 };
 
 export default TagTree;
diff --git a/frontend/shared/utils/LocalStorage.js b/frontend/shared/utils/LocalStorage.js
new file mode 100644
index 0000000000..267850f822
--- /dev/null
+++ b/frontend/shared/utils/LocalStorage.js
@@ -0,0 +1,40 @@
+class LocalStorageItem {
+    // Persistently store item once class is instantiated using `window.localStorage`.
+    constructor(key, defaultValue) {
+        this.key = key;
+        this.value = this.get();
+        if (this.value === null) {
+            this.set(defaultValue);
+        }
+    }
+    get() {
+        return window.localStorage.getItem(this.key);
+    }
+    set(value) {
+        this.value = value;
+        window.localStorage.setItem(this.key, value);
+    }
+}
+
+class LocalStorageBoolean extends LocalStorageItem {
+    // Storage designed specifically for boolean true/false fields. LocalStorage can only
+    // store string-values, so this takes care of casting item to a boolean.
+    get() {
+        const value = super.get();
+        switch (value) {
+            case "true":
+                return true;
+            case "false":
+                return false;
+            case null:
+                return null;
+            default:
+                throw `Unknown boolean value ${value}`;
+        }
+    }
+    toggle() {
+        this.set(!this.value);
+    }
+}
+
+export {LocalStorageBoolean, LocalStorageItem};
diff --git a/hawc/apps/assessment/api/viewsets.py b/hawc/apps/assessment/api/viewsets.py
index a853fdfea9..2f97377999 100644
--- a/hawc/apps/assessment/api/viewsets.py
+++ b/hawc/apps/assessment/api/viewsets.py
@@ -48,8 +48,8 @@ class CleanupFieldsBaseViewSet(
     Serializer should implement DynamicFieldsMixin.
     """
 
-    model: Model = None  # must be added
-    assessment_filter_args: str = ""  # must be added
+    model: type[Model]
+    assessment_filter_args: str
     filter_backends = (CleanupBulkIdFilter,)
     pagination_class = DisabledPagination
     permission_classes = (CleanupFieldsPermissions,)
diff --git a/hawc/apps/assessment/templates/assessment/assessment_downloads.html b/hawc/apps/assessment/templates/assessment/assessment_downloads.html
index d5f09c8c2c..148dabb2fc 100644
--- a/hawc/apps/assessment/templates/assessment/assessment_downloads.html
+++ b/hawc/apps/assessment/templates/assessment/assessment_downloads.html
@@ -12,7 +12,12 @@ <h2>{{object}} downloads</h2>
     {% if assessment.enable_literature_review %}
     <li>
         <b>Literature-review</b><br>
-        <a class='btn btn-primary' href="{% url 'lit:api:assessment-references-download' assessment.pk %}?format=xlsx">Download</a><br>
+        <a class='btn btn-primary' href="{% url 'lit:api:assessment-reference-export' assessment.pk %}?format=xlsx">Download</a><br>
+        {% if obj_perms.edit and assessment.literature_settings.conflict_resolution %}
+        <p class='form-text text-muted'>(no individual reviews)</p>
+        <a class='btn btn-primary' href="{% url 'lit:api:assessment-user-tag-export' assessment.pk %}?format=xlsx">Download complete</a>
+        <p class='form-text text-muted'>(includes individual reviews - only team members can view)</p>
+        {% endif %}
         <p class='form-text text-muted'>Microsoft Excel spreadsheet</p>
     </li>
     {% endif %}
@@ -20,14 +25,11 @@ <h2>{{object}} downloads</h2>
     {% if assessment.enable_risk_of_bias %}
     <li>
         <b>{{assessment.get_rob_name_display}} report</b><br>
-        <a class='btn btn-primary'
-           href="{% url 'riskofbias:api:assessment-export' assessment.pk %}?format=xlsx">Download</a><br>
-
+        <a class='btn btn-primary' href="{% url 'riskofbias:api:assessment-export' assessment.pk %}?format=xlsx">Download</a><br>
         {% if obj_perms.edit %}
         <p class='form-text text-muted'>(no individual reviews)</p>
-        <a class='btn btn-primary'
-           href="{% url 'riskofbias:api:assessment-full-export' assessment.pk %}?format=xlsx">Download complete</a>
-           <p class='form-text text-muted'>(includes individual reviews - team-members and higher only)</p>
+        <a class='btn btn-primary' href="{% url 'riskofbias:api:assessment-full-export' assessment.pk %}?format=xlsx">Download complete</a>
+        <p class='form-text text-muted'>(includes individual reviews - only team members can view)</p>
         {% endif %}
         <p class='form-text text-muted'>Microsoft Excel spreadsheet</p>
     </li>
diff --git a/hawc/apps/common/filterset.py b/hawc/apps/common/filterset.py
index 45253a3dfc..21cde30ca4 100644
--- a/hawc/apps/common/filterset.py
+++ b/hawc/apps/common/filterset.py
@@ -45,8 +45,9 @@ class AutocompleteModelChoiceFilter(df.ModelChoiceFilter):
 class GridColumn(BaseModel):
     rows: list[GridRow] = []
 
-    breakpoint: Optional[str]
+    breakpoint: str = ""
     width: Optional[int]
+    extra_css: str = ""
 
     def apply_layout(self, helper, index):
         if self.rows:
@@ -60,9 +61,10 @@ def apply_layout(self, helper, index):
 
     @property
     def css_class(self):
-        breakpoint = f"-{self.breakpoint}" if self.breakpoint is not None else ""
+        breakpoint = f"-{self.breakpoint}" if self.breakpoint else ""
         width = f"-{self.width}" if self.width is not None else ""
-        return f"col{breakpoint}{width}"
+        extra = f" {self.extra_css}" if self.extra_css else ""
+        return f"col{breakpoint}{width}{extra}"
 
 
 class GridRow(BaseModel):
@@ -215,7 +217,7 @@ def create_form(self) -> forms.Form:
         return form
 
 
-def dynamic_filterset(_class: BaseFilterSet, **meta_kwargs):
+def dynamic_filterset(_class: type[BaseFilterSet], **meta_kwargs):
     default_kwargs = _class._meta.__dict__
     Meta = type("Meta", (object,), {**default_kwargs, **meta_kwargs})
     return type("DynamicFilterset", (_class,), {"Meta": Meta})
diff --git a/hawc/apps/common/forms.py b/hawc/apps/common/forms.py
index f5684369a2..cc30140bc1 100644
--- a/hawc/apps/common/forms.py
+++ b/hawc/apps/common/forms.py
@@ -62,6 +62,7 @@ class BaseFormHelper(cf.FormHelper):
     error_text_inline = False
     use_custom_control = True
     include_media = False
+    field_template = "crispy_forms/layout/field.html"
 
     def __init__(self, form=None, **kwargs):
         self.attrs = {}
diff --git a/hawc/apps/common/views.py b/hawc/apps/common/views.py
index 32afa1b274..d039714e4c 100644
--- a/hawc/apps/common/views.py
+++ b/hawc/apps/common/views.py
@@ -687,7 +687,7 @@ def get_context_data(self, **kwargs):
 
 
 class BaseFilterList(BaseList):
-    filterset_class: Type[BaseFilterSet]
+    filterset_class: type[BaseFilterSet]
     paginate_by = 25
 
     def get_paginate_by(self, qs) -> int:
@@ -702,10 +702,13 @@ def get_filterset_kwargs(self):
             assessment=self.assessment,
         )
 
+    def get_filterset_class(self) -> type[BaseFilterSet]:
+        return self.filterset_class
+
     @property
     def filterset(self):
         if not hasattr(self, "_filterset"):
-            self._filterset = self.filterset_class(**self.get_filterset_kwargs())
+            self._filterset = self.get_filterset_class()(**self.get_filterset_kwargs())
         return self._filterset
 
     def get_queryset(self):
diff --git a/hawc/apps/lit/admin.py b/hawc/apps/lit/admin.py
index b87e459fdc..e133a8dc7b 100644
--- a/hawc/apps/lit/admin.py
+++ b/hawc/apps/lit/admin.py
@@ -10,18 +10,19 @@ class LiteratureAssessmentAdmin(admin.ModelAdmin):
     form = forms.LiteratureAssessmentForm
     readonly_fields = ("assessment", "topic_tsne_refresh_requested", "topic_tsne_last_refresh")
     list_display = (
-        "assessment",
+        "assessment_id",
+        "conflict_resolution",
         "extraction_tag",
-        "topic_tsne_data",
         "topic_tsne_refresh_requested",
         "topic_tsne_last_refresh",
     )
-    list_select_related = ("assessment", "extraction_tag")
+    list_filter = ("conflict_resolution",)
+    list_select_related = ("extraction_tag",)
 
 
 @admin.register(models.ReferenceFilterTag)
 class ReferenceFilterTagAdmin(TreeAdmin):
-    pass
+    readonly_fields = ("path", "depth", "numchild")
 
 
 @admin.register(models.Search)
@@ -91,3 +92,12 @@ class ReferenceAdmin(admin.ModelAdmin):
     list_filter = ("year", ("assessment", admin.RelatedOnlyFieldListFilter))
     raw_id_fields = ("searches", "identifiers")
     search_fields = ("title", "authors", "year")
+
+
+@admin.register(models.UserReferenceTag)
+class UserReferenceTagAdmin(admin.ModelAdmin):
+    raw_id_fields = ("reference",)
+    list_display = ("id", "user", "reference", "created", "last_updated")
+    list_filter = ("last_updated",)
+    list_select_related = ("user", "reference")
+    readonly_fields = ("created", "last_updated")
diff --git a/hawc/apps/lit/api.py b/hawc/apps/lit/api.py
index f4b6f2282e..4e1186ac5f 100644
--- a/hawc/apps/lit/api.py
+++ b/hawc/apps/lit/api.py
@@ -5,10 +5,11 @@
 from django.conf import settings
 from django.core.cache import cache
 from django.db import transaction
+from django.shortcuts import get_object_or_404
 from django.utils import timezone
 from rest_framework import exceptions, mixins, permissions, status, viewsets
 from rest_framework.decorators import action
-from rest_framework.exceptions import ParseError, ValidationError
+from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError
 from rest_framework.parsers import FileUploadParser
 from rest_framework.response import Response
 
@@ -25,7 +26,7 @@
 from ..common.renderers import PandasRenderers
 from ..common.serializers import UnusedSerializer
 from ..common.views import create_object_log
-from . import exports, models, serializers
+from . import exports, filterset, models, serializers
 
 
 class LiteratureAssessmentViewset(viewsets.GenericViewSet):
@@ -73,6 +74,8 @@ def tagtree(self, request, pk, *args, **kwargs):
             create_object_log(
                 "Updated (tagtree replace)", assessment, assessment.id, self.request.user.id
             )
+        else:
+            raise ValueError()
         return Response(serializer.data)
 
     @action(
@@ -219,23 +222,66 @@ def topic_model_request_refresh(self, request, pk):
 
     @action(
         detail=True,
-        url_path="references-download",
+        url_path="reference-export",
         action_perms=AssessmentViewSetPermissions.CAN_VIEW_OBJECT,
         renderer_classes=PandasRenderers,
     )
-    def references_download(self, request, pk):
+    def reference_export(self, request, pk):
         """
         Get all references in an assessment.
         """
         assessment = self.get_object()
+        queryset = (
+            models.Reference.objects.get_qs(assessment)
+            .prefetch_related("identifiers", "tags")
+            .order_by("id")
+        )
+        fs = filterset.ReferenceExportFilterSet(
+            data=request.query_params,
+            queryset=queryset,
+            request=request,
+        )
+        if not fs.is_valid():
+            raise ValidationError(fs.errors)
+
         tags = models.ReferenceFilterTag.get_all_tags(assessment.id)
+        Exporter = (
+            exports.TableBuilderFormat
+            if request.query_params.get("export_format") == "table-builder"
+            else exports.ReferenceFlatComplete
+        )
+        export = Exporter(
+            queryset=fs.qs,
+            filename=f"references-{assessment.name}",
+            assessment=assessment,
+            tags=tags,
+        )
+        return Response(export.build_export())
+
+    @action(
+        detail=True,
+        url_path="user-tag-export",
+        renderer_classes=PandasRenderers,
+        action_perms=AssessmentViewSetPermissions.TEAM_MEMBER_OR_HIGHER,
+    )
+    def user_tag_export(self, request, pk):
+        """
+        Get all references in an assessment.
+        """
+        assessment = self.get_object()
+        tags = models.ReferenceFilterTag.get_all_tags(assessment.id)
+        qs = (
+            models.UserReferenceTag.objects.filter(reference__assessment=assessment.id)
+            .select_related("reference", "user")
+            .prefetch_related("tags", "reference__identifiers")
+            .order_by("reference_id", "id")
+        )
         exporter = exports.ReferenceFlatComplete(
-            models.Reference.objects.get_qs(assessment)
-            .prefetch_related("identifiers")
-            .order_by("id"),
-            filename=f"references-{assessment}",
+            qs,
+            filename=f"references-user-tags-{assessment.name}",
             assessment=assessment,
             tags=tags,
+            user_tags=True,
         )
         return Response(exporter.build_export())
 
@@ -341,59 +387,11 @@ class SearchViewset(mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets
     def get_queryset(self):
         return self.model.objects.all()
 
-    @action(
-        detail=True,
-        action_perms=AssessmentViewSetPermissions.CAN_VIEW_OBJECT,
-        renderer_classes=PandasRenderers,
-    )
-    def references(self, request, pk):
-        """
-        Return all references for a given Search
-        """
-        instance = self.get_object()
-        exporter = exports.ReferenceFlatComplete(
-            instance.references.all(),
-            filename=f"{instance.assessment}-search-{instance.slug}",
-            assessment=self.assessment,
-            tags=models.ReferenceFilterTag.get_all_tags(self.assessment.id),
-            include_parent_tag=False,
-        )
-        return Response(exporter.build_export())
-
 
 class ReferenceFilterTagViewset(AssessmentRootedTagTreeViewset):
     model = models.ReferenceFilterTag
     serializer_class = serializers.ReferenceFilterTagSerializer
 
-    @action(
-        detail=True,
-        action_perms=AssessmentViewSetPermissions.CAN_VIEW_OBJECT,
-        renderer_classes=PandasRenderers,
-    )
-    def references(self, request, pk):
-        """
-        Return all references for a selected tag, including tag-descendants.
-        """
-        tag = self.get_object()
-        serializer = serializers.ReferenceTagExportSerializer(data=request.query_params)
-        if serializer.is_valid():
-            qs = (
-                models.Reference.objects.all()
-                .with_tag(tag=tag, descendants=serializer.include_descendants())
-                .order_by("id")
-            )
-            ExportClass = serializer.get_exporter()
-            exporter = ExportClass(
-                queryset=qs,
-                filename=f"{self.assessment}-{tag.slug}",
-                assessment=self.assessment,
-                tags=self.model.get_all_tags(self.assessment.id),
-                include_parent_tag=False,
-            )
-            return Response(exporter.build_export())
-        else:
-            return Response(serializer.errors, status=400)
-
 
 class ReferenceCleanupViewset(CleanupFieldsBaseViewSet):
     serializer_class = serializers.ReferenceCleanupFieldsSerializer
@@ -413,17 +411,43 @@ class ReferenceViewset(
     action_perms = {}
     queryset = models.Reference.objects.all()
 
+    def get_queryset(self):
+        qs = super().get_queryset()
+        if self.action in ("tag", "resolve_conflict"):
+            qs = qs.select_related("assessment__literature_settings").prefetch_related(
+                "user_tags__tags", "tags"
+            )
+        return qs
+
     @action(
         detail=True, methods=("post",), action_perms=AssessmentViewSetPermissions.CAN_EDIT_OBJECT
     )
     def tag(self, request, pk):
         response = {"status": "fail"}
-        ref = self.get_object()
-        assessment = ref.assessment
+        instance = self.get_object()
+        assessment = instance.assessment
         if assessment.user_can_edit_object(self.request.user):
-            tag_pks = self.request.POST.getlist("tags[]", [])
-            ref.tags.set(tag_pks)
-            ref.last_updated = timezone.now()
-            ref.save()
+            try:
+                tags = [int(tag) for tag in self.request.data.get("tags", [])]
+                resolved = instance.update_tags(request.user, tags)
+            except ValueError:
+                return Response({"tags": "Array of tags must be valid primary keys"}, status=400)
             response["status"] = "success"
+            response["resolved"] = resolved
         return Response(response)
+
+    @action(
+        detail=True, methods=("post",), action_perms=AssessmentViewSetPermissions.CAN_EDIT_OBJECT
+    )
+    def resolve_conflict(self, request, pk):
+        instance = self.get_object()
+        assessment = instance.assessment
+        if not assessment.user_can_edit_object(self.request.user):
+            raise PermissionDenied()
+        user_reference_tag = get_object_or_404(
+            models.UserReferenceTag,
+            reference_id=instance.id,
+            id=int(request.POST.get("user_tag_id", -1)),
+        )
+        instance.resolve_user_tag_conflicts(self.request.user.id, user_reference_tag)
+        return Response({"status": "ok"})
diff --git a/hawc/apps/lit/exports.py b/hawc/apps/lit/exports.py
index 44320d5d63..eae50b0d86 100644
--- a/hawc/apps/lit/exports.py
+++ b/hawc/apps/lit/exports.py
@@ -1,5 +1,3 @@
-from copy import copy, deepcopy
-
 from django.utils.html import strip_tags
 
 from ..common.helper import FlatFileExporter
@@ -12,9 +10,6 @@ class ReferenceFlatComplete(FlatFileExporter):
     """
 
     def _get_header_row(self):
-        tags = self.kwargs.get("tags", None)
-        include_parent_tag = self.kwargs.get("include_parent_tag", False)
-
         headers = [
             "HAWC ID",
             "HERO ID",
@@ -32,91 +27,83 @@ def _get_header_row(self):
             "Created",
             "Last updated",
         ]
-        if tags:
+        if self.kwargs.get("user_tags", False):
             headers.extend(
-                models.ReferenceFilterTag.get_flattened_taglist(tags, include_parent_tag)
+                [
+                    "User Tag ID",
+                    "User Tag Author ID",
+                    "User Tag Author",
+                    "User Tags Resolved",
+                    "User Tags Last Updated",
+                ]
             )
-        return headers
-
-    def _get_data_rows(self):
-        rows = []
-
-        def resetTags(tags):
-            def setFalse(obj):
-                obj["isTagged"] = False
-                for child in obj.get("children", []):
-                    setFalse(child)
-
-            tagsCopy = deepcopy(tags)
-            setFalse(tagsCopy)
-            return tagsCopy
 
-        tags_base = resetTags(self.kwargs.get("tags")[0])
-        include_parent_tag = self.kwargs.get("include_parent_tag", False)
-
-        def getTagRow(tags):
-            row = []
-
-            def printStatus(obj):
-                row.append(obj["isTagged"])
-                for child in obj.get("children", []):
-                    printStatus(child)
-
-            if include_parent_tag:
-                printStatus(tags)
-            else:
-                for child in tags.get("children", []):
-                    printStatus(child)
-            return row
-
-        def applyTags(tagslist, ref):
-            def applyTag(tagged, tagslist):
-                def checkMatch(tagged, tag, parents):
-                    parents = copy(parents)
-                    if tagged.id == tag["id"]:
-                        tag["isTagged"] = True
-                        for parent in parents:
-                            parent["isTagged"] = True
-
-                    parents.append(tag)
-                    for child in tag.get("children", []):
-                        checkMatch(tagged, child, parents)
+        headers.extend(models.ReferenceFilterTag.get_flattened_taglist(self.kwargs["tags"]))
+        return headers
 
-                if include_parent_tag:
-                    checkMatch(tagged, tagslist, [])
-                else:
-                    for child in tagslist.get("children", []):
-                        checkMatch(tagged, child, [])
+    def _get_reference_data(self, ref: models.Reference) -> list:
+        return [
+            ref.pk,
+            ref.get_hero_id(),
+            ref.get_pubmed_id(),
+            ref.get_doi_id(),
+            ref.ref_short_citation,
+            ref.ref_full_citation,
+            ref.title,
+            ref.authors,
+            ref.authors_short,
+            ref.year,
+            ref.journal,
+            strip_tags(ref.abstract),
+            ref.full_text_url,
+            ref.created,
+            ref.last_updated,
+        ]
 
-            for tag in ref.tags.all():
-                applyTag(tag, tagslist)
+    def _get_data_rows(self) -> list[list]:
+        tag_tree = models.ReferenceFilterTag.get_tree_descendants(self.kwargs["tags"])
+        user_tags = self.kwargs.get("user_tags", False)
+        func = self._get_reference_rows_with_users if user_tags else self._get_reference_rows
+        print(tag_tree)
+        return func(tag_tree)
 
-        row = []
+    def _get_reference_rows(
+        self, tag_tree: models.ReferenceFilterTag.TreeDescendantType
+    ) -> list[list]:
+        rows = []
         for ref in self.queryset:
-            row = [
-                ref.pk,
-                ref.get_hero_id(),
-                ref.get_pubmed_id(),
-                ref.get_doi_id(),
-                ref.ref_short_citation,
-                ref.ref_full_citation,
-                ref.title,
-                ref.authors,
-                ref.authors_short,
-                ref.year,
-                ref.journal,
-                strip_tags(ref.abstract),
-                ref.full_text_url,
-                ref.created,
-                ref.last_updated,
-            ]
-
-            tagsCopy = deepcopy(tags_base)
-            applyTags(tagsCopy, ref)
-            row.extend(getTagRow(tagsCopy))
-
+            row = self._get_reference_data(ref)
+            reference_tags = set(tag.id for tag in ref.tags.all())
+            # for each tag in tree, check to see if this item has been tagged with this
+            # tag or its descendants
+            row.extend(
+                [any(tag in tag_set for tag in reference_tags) for tag_set in tag_tree.values()]
+            )
             rows.append(row)
+        return rows
 
+    def _get_reference_rows_with_users(
+        self, tag_tree: models.ReferenceFilterTag.TreeDescendantType
+    ) -> list[list]:
+        rows = []
+        for user_tag in self.queryset:
+            row = self._get_reference_data(user_tag.reference)
+            row.extend(
+                [
+                    user_tag.id,
+                    user_tag.user.id,
+                    user_tag.user.get_full_name(),
+                    user_tag.is_resolved,
+                    user_tag.last_updated,
+                ]
+            )
+            user_tags_applied = set(tag.id for tag in user_tag.tags.all())
+            # for each tag in tree, check to see if this item has been tagged with this
+            # tag or its descendants
+            row.extend(
+                [any(tag in tag_set for tag in user_tags_applied) for tag_set in tag_tree.values()]
+            )
+            rows.append(row)
         return rows
 
 
diff --git a/hawc/apps/lit/filterset.py b/hawc/apps/lit/filterset.py
index 34377a8d24..23e2bb5d0f 100644
--- a/hawc/apps/lit/filterset.py
+++ b/hawc/apps/lit/filterset.py
@@ -1,7 +1,9 @@
 import django_filters as df
-from django.db.models import Q
+from django.db.models import Count, Q
+from django.forms.widgets import CheckboxInput
+from django_filters import FilterSet
 
-from ..common.filterset import BaseFilterSet, PaginationFilter
+from ..common.filterset import BaseFilterSet, PaginationFilter, filter_noop
 from . import models
 
 
@@ -17,18 +19,69 @@ class ReferenceFilterSet(BaseFilterSet):
     journal = df.CharFilter(lookup_expr="icontains", label="Journal")
     title_abstract = df.CharFilter(method="filter_title_abstract", label="Title/Abstract")
     authors = df.CharFilter(method="filter_authors", label="Authors")
+    search = df.ModelChoiceFilter(
+        field_name="searches", queryset=models.Search.objects.all(), label="Search/Import"
+    )
     tags = df.ModelMultipleChoiceFilter(
         queryset=models.ReferenceFilterTag.objects.all(),
+        null_value="untagged",
+        null_label="[Untagged]",
         method="filter_tags",
         conjoined=True,
         label="Tags",
-        help_text="Select one or more tags. If a parent tag is selected, tag children are also considered a match. If multiple tags are selected, references must include all selected tags (or their children).",
+        help_text="Select a tag to view references with that specific tag. Choose [Untagged] to view references without any tags. If multiple tags are selected, references must include all selected tags.",
+    )
+    include_descendants = df.BooleanFilter(
+        method=filter_noop,
+        widget=CheckboxInput(),
+        label="Include tag descendants",
+        help_text="Applies to tags selected above. By default, only references with the specific selected tag(s) are shown; checking this box includes references that are tagged with any descendant of the selected tag",
+    )
+    anything_tagged = df.BooleanFilter(
+        method="filter_anything_tagged",
+        widget=CheckboxInput(),
+        label="Anything tagged",
+        help_text="Check box to view references with at least one tag applied",
+    )
+    my_tags = df.ModelMultipleChoiceFilter(
+        queryset=models.ReferenceFilterTag.objects.all(),
+        null_value="untagged",
+        null_label="[Untagged]",
+        method="filter_my_tags",
+        conjoined=True,
+        label="My Tags",
+        help_text="Select a tag to view references you have applied that specific tag to. Choose [Untagged] to view references that you have not tagged. If multiple tags are selected, references must include all selected tags.",
+    )
+    include_mytag_descendants = df.BooleanFilter(
+        method=filter_noop,
+        widget=CheckboxInput(),
+        label="Include tag descendants",
+        help_text="Applies to tags selected above. By default, only references with the specific selected tag(s) are shown; checking this box includes references that are tagged with any descendant of the selected tag",
+    )
+    anything_tagged_me = df.BooleanFilter(
+        method="filter_anything_tagged_me",
+        widget=CheckboxInput(),
+        label="Tagged by me",
+        help_text="All references that you have tagged",
     )
     order_by = df.OrderingFilter(
         fields=(
             ("authors", "authors"),
             ("year", "year"),
         ),
+        help_text="How results will be ordered",
+    )
+    needs_tagging = df.BooleanFilter(
+        method="filter_needs_tagging",
+        widget=CheckboxInput(),
+        label="Needs Tagging",
+        help_text="References tagged by less than two people",
+    )
+    partially_tagged = df.BooleanFilter(
+        method="filter_partially_tagged",
+        widget=CheckboxInput(),
+        label="Partially Tagged",
+        help_text="References with one unresolved user tag",
     )
     paginate_by = PaginationFilter()
 
@@ -41,9 +94,17 @@ class Meta:
             "journal",
             "title_abstract",
             "authors",
+            "search",
+            "tags",
+            "include_descendants",
+            "anything_tagged",
+            "my_tags",
+            "include_mytag_descendants",
+            "anything_tagged_me",
             "order_by",
             "paginate_by",
-            "tags",
+            "partially_tagged",
+            "needs_tagging",
         ]
 
     def filter_queryset(self, queryset):
@@ -59,15 +120,129 @@ def filter_title_abstract(self, queryset, name, value):
         return queryset.filter(query)
 
     def filter_tags(self, queryset, name, value):
+        include_descendants = self.data.get("include_descendants", False)
+
+        for tag in value:
+            if tag == "untagged":
+                queryset = queryset.filter(tags__isnull=True)
+            else:
+                tag_ids = (
+                    list(tag.get_tree(parent=tag).values_list("id", flat=True))
+                    if include_descendants
+                    else [tag.id]
+                )
+                queryset = queryset.filter(tags__in=tag_ids)
+        return queryset.distinct()
+
+    def filter_anything_tagged(self, queryset, name, value):
+        if not value:
+            return queryset
+        queryset = queryset.filter(tags__isnull=False)
+        return queryset.distinct()
+
+    def filter_my_tags(self, queryset, name, value):
+        include_descendants = self.data.get("include_mytag_descendants", False)
         for tag in value:
-            tag_ids = list(tag.get_tree(parent=tag).values_list("id", flat=True))
-            queryset = queryset.filter(tags__in=tag_ids)
+            if tag == "untagged":
+                queryset = queryset.annotate(
+                    user_tag_count=Count(
+                        "user_tags",
+                        filter=Q(user_tags__is_resolved=False)
+                        & Q(user_tags__user=self.request.user),
+                    )
+                ).filter(user_tag_count=0)
+            else:
+                tag_ids = (
+                    list(tag.get_tree(parent=tag).values_list("id", flat=True))
+                    if include_descendants
+                    else [tag.id]
+                )
+                queryset = queryset.annotate(
+                    mytag_count=Count(
+                        "user_tags",
+                        filter=Q(user_tags__tags__in=tag_ids)
+                        & Q(user_tags__is_resolved=False)
+                        & Q(user_tags__user=self.request.user),
+                    )
+                ).filter(mytag_count__gt=0)
+        return queryset.distinct()
+
+    def filter_anything_tagged_me(self, queryset, name, value):
+        if not value:
+            return queryset
+        queryset = queryset.annotate(
+            my_tag_count=Count(
+                "user_tags",
+                filter=Q(user_tags__is_resolved=False) & Q(user_tags__user=self.request.user),
+            )
+        ).filter(my_tag_count__gt=0)
+        return queryset.distinct()
+
+    def filter_partially_tagged(self, queryset, name, value):
+        if not value:
+            return queryset
+        queryset = queryset.annotate(
+            user_tag_count=Count("user_tags", filter=Q(user_tags__is_resolved=False))
+        ).filter(user_tag_count=1)
+        return queryset.distinct()
+
+    def filter_needs_tagging(self, queryset, name, value):
+        if not value:
+            return queryset
+        queryset = queryset.annotate(
+            user_tag_count=Count("user_tags", filter=Q(user_tags__is_resolved=False))
+        )
+        queryset = queryset.filter(user_tag_count__lt=2, tags__isnull=True)
         return queryset.distinct()
 
     def create_form(self):
         form = super().create_form()
-        tags = models.ReferenceFilterTag.get_assessment_qs(self.assessment.id)
-        form.fields["tags"].queryset = tags
-        form.fields["tags"].label_from_instance = lambda tag: tag.get_nested_name()
-        form.fields["tags"].widget.attrs["size"] = 8
+        for field in [
+            "partially_tagged",
+            "tags",
+            "my_tags",
+            "anything_tagged",
+            "anything_tagged_me",
+            "include_mytag_descendants",
+            "include_descendants",
+            "needs_tagging",
+        ]:
+            if field in form.fields:
+                form.fields[field].hover_help = True
+        if "tags" in form.fields:
+            tags = models.ReferenceFilterTag.get_assessment_qs(self.assessment.id)
+            form.fields["tags"].queryset = tags
+            form.fields["tags"].label_from_instance = lambda tag: tag.get_nested_name()
+            form.fields["tags"].widget.attrs["size"] = 8
+        if "my_tags" in form.fields:
+            tags = models.ReferenceFilterTag.get_assessment_qs(self.assessment.id)
+            form.fields["my_tags"].queryset = tags
+            form.fields["my_tags"].label_from_instance = lambda tag: tag.get_nested_name()
+            form.fields["my_tags"].widget.attrs["size"] = 8
+            form.fields["tags"].label = "Consensus Tags"
+        if "search" in form.fields:
+            form.fields["search"].queryset = models.Search.objects.filter(
+                assessment=self.assessment
+            )
         return form
+
+
+class ReferenceExportFilterSet(FilterSet):
+    searches = df.ModelChoiceFilter(queryset=models.Search.objects.all())
+    tag = df.ModelChoiceFilter(
+        queryset=models.ReferenceFilterTag.objects.all(), method="filter_tag"
+    )
+    include_descendants = df.BooleanFilter(method=filter_noop)
+    table_builder = df.BooleanFilter(method=filter_noop)
+
+    class Meta:
+        model = models.Reference
+        fields = [
+            "searches",
+            "tag",
+            "include_descendants",
+        ]
+
+    def filter_tag(self, queryset, name, value):
+        include_descendants = self.data.get("include_descendants", False)
+        return queryset.with_tag(tag=value, descendants=include_descendants)
diff --git a/hawc/apps/lit/forms.py b/hawc/apps/lit/forms.py
index 64c80a676e..9962c060a9 100644
--- a/hawc/apps/lit/forms.py
+++ b/hawc/apps/lit/forms.py
@@ -34,17 +34,44 @@ def __init__(self, *args, **kwargs):
 
     class Meta:
         model = models.LiteratureAssessment
-        fields = ("extraction_tag",)
+        fields = (
+            "conflict_resolution",
+            "extraction_tag",
+            "screening_instructions",
+            "name_list_1",
+            "color_list_1",
+            "keyword_list_1",
+            "name_list_2",
+            "color_list_2",
+            "keyword_list_2",
+            "name_list_3",
+            "color_list_3",
+            "keyword_list_3",
+        )
+        field_classes = {
+            "screening_instructions": QuillField,
+        }
+        widgets = {
+            "color_list_1": forms.TextInput(attrs={"type": "color"}),
+            "color_list_2": forms.TextInput(attrs={"type": "color"}),
+            "color_list_3": forms.TextInput(attrs={"type": "color"}),
+        }
 
     def setHelper(self):
         if self.instance.id:
             inputs = {
                 "legend_text": "Update literature assessment settings",
                 "help_text": "Update literature settings for this assessment",
-                "cancel_url": reverse_lazy("lit:tags_update", args=(self.instance.assessment_id,)),
+                "cancel_url": reverse_lazy("lit:overview", args=(self.instance.assessment_id,)),
             }
 
         helper = BaseFormHelper(self, **inputs)
+        for fld in ("keyword_list_1", "keyword_list_2", "keyword_list_3"):
+            self.fields[fld].widget.attrs["rows"] = 3
+        helper.add_row("conflict_resolution", 2, "col-md-6")
+        helper.add_row("name_list_1", 3, ["col-md-3 pr-3", "col-md-2 px-2", "col-md-7 pl-3"])
+        helper.add_row("name_list_2", 3, ["col-md-3 pr-3", "col-md-2 px-2", "col-md-7 pl-3"])
+        helper.add_row("name_list_3", 3, ["col-md-3 pr-3", "col-md-2 px-2", "col-md-7 pl-3"])
         return helper
 
 
@@ -509,18 +536,6 @@ def save(self, commit=True):
         return instance
 
 
-class ReferenceFilterTagForm(forms.ModelForm):
-    class Meta:
-        model = models.ReferenceFilterTag
-        fields = "__all__"
-
-
-class TagReferenceForm(forms.ModelForm):
-    class Meta:
-        model = models.Reference
-        fields = ("tags",)
-
-
 class TagsCopyForm(forms.Form):
 
     assessment = forms.ModelChoiceField(queryset=Assessment.objects.all(), empty_label=None)
diff --git a/hawc/apps/lit/managers.py b/hawc/apps/lit/managers.py
index bac9d22e02..5770435d2f 100644
--- a/hawc/apps/lit/managers.py
+++ b/hawc/apps/lit/managers.py
@@ -3,10 +3,11 @@
 
 import pandas as pd
 from django.apps import apps
+from django.contrib.postgres.aggregates import ArrayAgg
 from django.core.exceptions import ValidationError
 from django.core.validators import URLValidator
 from django.db import models
-from django.db.models import Q, QuerySet
+from django.db.models import Count, Q, QuerySet
 from django.db.models.functions import Cast
 from taggit.managers import TaggableManager, _TaggableManager
 from taggit.utils import require_instance_manager
@@ -48,6 +49,9 @@ def set(self, tag_pks):
         full_taglist = self.through.tag_model().get_descendants_pks(self.instance.assessment_id)
         selected_tags = set(tag_pks).intersection(full_taglist)
 
+        if len(selected_tags) < len(tag_pks):
+            raise ValueError("At least one of the given tags belongs to a different assessment.")
+
         tagrefs = []
         for tag_id in selected_tags:
             tagrefs.append(self.through(tag_id=tag_id, content_object=self.instance))
@@ -496,6 +500,22 @@ def prune_tags(self, root_tag, pruned_tags, descendants: bool = False):
         )
         return self.exclude(query).distinct("pk")
 
+    def unresolved_user_tags(self, user_id: int) -> dict[int, list[int]]:
+        # Return a dictionary of reference_id: list[tag_ids] items for all references in a queryset
+        # TODO - update to annotate queryset with Django 4.1?
+        # https://docs.djangoproject.com/en/4.1/ref/contrib/postgres/expressions/#arraysubquery-expressions
+        UserReferenceTag = apps.get_model("lit", "UserReferenceTag")
+        user_qs = (
+            UserReferenceTag.objects.filter(reference__in=self, user=user_id, is_resolved=False)
+            .annotate(tag_ids=ArrayAgg("tags__id"))
+            .values_list("reference_id", "tag_ids")
+        )
+        # ArrayAgg can return [None] if some cases; filter to remove
+        return {
+            reference_id: [tag for tag in tag_ids if tag is not None]
+            for reference_id, tag_ids in user_qs
+        }
+
 
 class ReferenceManager(BaseManager):
 
@@ -578,13 +598,13 @@ def get_hero_references(self, search, identifiers):
 
         return refs
 
-    def get_overview_details(self, assessment):
+    def get_overview_details(self, assessment) -> dict[str, int]:
         # Get an overview of tagging progress for an assessment
         refs = self.get_qs(assessment)
         total = refs.count()
         total_tagged = refs.annotate(tag_count=models.Count("tags")).filter(tag_count__gt=0).count()
         total_untagged = total - total_tagged
-        total_searched = refs.filter(searches__search_type="s").distinct().count()
+        total_searched = refs.all().filter(searches__search_type="s").distinct().count()
         total_imported = total - total_searched
         overview = {
             "total_references": total,
@@ -593,6 +613,29 @@ def get_overview_details(self, assessment):
             "total_searched": total_searched,
             "total_imported": total_imported,
         }
+        if assessment.literature_settings.conflict_resolution:
+            UserReferenceTag = apps.get_model("lit", "UserReferenceTag")
+            user_refs = UserReferenceTag.objects.filter(reference__in=refs)
+            overview.update(
+                needs_tagging=(
+                    refs.annotate(
+                        user_tag_count=Count("user_tags", filter=Q(user_tags__is_resolved=False))
+                    )
+                    .filter(user_tag_count__lt=2)
+                    .count()
+                ),
+                conflicts=(
+                    refs.annotate(
+                        n_unapplied_reviews=Count(
+                            "user_tags", filter=Q(user_tags__is_resolved=False)
+                        )
+                    )
+                    .filter(n_unapplied_reviews__gt=1)
+                    .count()
+                ),
+                total_reviews=user_refs.count(),
+                total_users=user_refs.distinct("user_id").count(),
+            )
         return overview
 
     def get_pubmed_references(self, search, identifiers):
@@ -874,3 +917,7 @@ def delete_orphan_tags(self, assessment_id) -> tuple[int, int]:
             message=json.dumps({"count": number_deleted, "data": deleted_data}),
         )
         return number_deleted, log.id
+
+
+class UserReferenceTagsManager(BaseManager):
+    assessment_relation = "content_object__reference__assessment"
diff --git a/hawc/apps/lit/migrations/0017_conflict_resolution.py b/hawc/apps/lit/migrations/0017_conflict_resolution.py
new file mode 100644
index 0000000000..3daf86884a
--- /dev/null
+++ b/hawc/apps/lit/migrations/0017_conflict_resolution.py
@@ -0,0 +1,210 @@
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+from .. import managers
+
+
+def disable_conflict_resolution(apps, schema_editor):
+    # Do not enable conflict resolution on existing apps
+    apps.get_model("lit", "LiteratureAssessment").objects.update(conflict_resolution=False)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ("lit", "0016_doi_lowercase"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="UserReferenceTag",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+                    ),
+                ),
+                (
+                    "is_resolved",
+                    models.BooleanField(
+                        default=False,
+                        help_text="User specific tag differences are resolved for this reference",
+                    ),
+                ),
+                ("created", models.DateTimeField(auto_now_add=True)),
+                ("last_updated", models.DateTimeField(auto_now=True)),
+                (
+                    "reference",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="user_tags",
+                        to="lit.reference",
+                    ),
+                ),
+            ],
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="conflict_resolution",
+            field=models.BooleanField(
+                default=settings.HAWC_FEATURES.DEFAULT_LITERATURE_CONFLICT_RESOLUTION,
+                help_text="Enable conflict resolution for reference screening. If enabled, at least two reviewers must independently review and tag literature, and tag conflicts must be resolved before tags are applied to a reference. If disabled, tags are immediately applied to references.  We do not recommend changing this setting after screening has begun.",
+                verbose_name="Conflict resolution required",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="screening_instructions",
+            field=models.TextField(
+                blank=True,
+                help_text="Add instructions for screeners. This information will be shown on the\n        literature screening page and will publicly available, if the assessment is made public.",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="literatureassessment",
+            name="extraction_tag",
+            field=models.ForeignKey(
+                blank=True,
+                help_text="References tagged with this tag or its descendants will be available for data extraction and study quality/risk of bias evaluation.",
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to="lit.referencefiltertag",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="name_list_1",
+            field=models.CharField(
+                default="Positive",
+                help_text="Name for this list of keywords",
+                max_length=64,
+                verbose_name="Name List 1",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="color_list_1",
+            field=models.CharField(
+                default="#228833",
+                help_text="Keywords in list 1 will be highlighted this color",
+                max_length=7,
+                verbose_name="Highlight Color 1",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="keyword_list_1",
+            field=models.TextField(
+                blank=True,
+                help_text='Keywords to highlight in titles and abstracts on the reference tagging page.\n         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include\n         commas.',
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="color_list_2",
+            field=models.CharField(
+                default="#EE6677",
+                help_text="Keywords in list 2 will be highlighted this color",
+                max_length=7,
+                verbose_name="Highlight Color 2",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="name_list_2",
+            field=models.CharField(
+                default="Negative",
+                help_text="Name for this list of keywords",
+                max_length=64,
+                verbose_name="Name List 2",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="keyword_list_2",
+            field=models.TextField(
+                blank=True,
+                help_text='Keywords to highlight in titles and abstracts on the reference tagging page.\n         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include\n         commas.',
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="name_list_3",
+            field=models.CharField(
+                default="Additional",
+                help_text="Name for this list of keywords",
+                max_length=64,
+                verbose_name="Name List 3",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="color_list_3",
+            field=models.CharField(
+                default="#4477AA",
+                help_text="Keywords in list 3 will be highlighted this color",
+                max_length=7,
+                verbose_name="Highlight Color 3",
+            ),
+        ),
+        migrations.AddField(
+            model_name="literatureassessment",
+            name="keyword_list_3",
+            field=models.TextField(
+                blank=True,
+                help_text='Keywords to highlight in titles and abstracts on the reference tagging page.\n         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include\n         commas.',
+            ),
+        ),
+        migrations.CreateModel(
+            name="UserReferenceTags",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+                    ),
+                ),
+                (
+                    "content_object",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="lit.userreferencetag"
+                    ),
+                ),
+                (
+                    "tag",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="user_references",
+                        to="lit.referencefiltertag",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.AddField(
+            model_name="userreferencetag",
+            name="tags",
+            field=managers.ReferenceFilterTagManager(
+                blank=True,
+                help_text="A comma-separated list of tags.",
+                through="lit.UserReferenceTags",
+                to="lit.ReferenceFilterTag",
+                verbose_name="Tags",
+            ),
+        ),
+        migrations.AddField(
+            model_name="userreferencetag",
+            name="user",
+            field=models.ForeignKey(
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="reference_tags",
+                to=settings.AUTH_USER_MODEL,
+            ),
+        ),
+        migrations.RunPython(disable_conflict_resolution, reverse_code=migrations.RunPython.noop),
+    ]
diff --git a/hawc/apps/lit/models.py b/hawc/apps/lit/models.py
index fa7ec60025..c8ba9b8f91 100644
--- a/hawc/apps/lit/models.py
+++ b/hawc/apps/lit/models.py
@@ -3,6 +3,7 @@
 import logging
 import pickle
 import re
+from copy import copy
 from io import BytesIO
 from math import ceil
 from urllib import parse
@@ -23,10 +24,12 @@
 from taggit.models import ItemBase
 from treebeard.mp_tree import MP_Node
 
+from ...constants import ColorblindColors
 from ...refml import topics
 from ...services.nih import pubmed
 from ...services.utils import ris
 from ...services.utils.doi import get_doi_from_identifier, try_get_doi
+from ..assessment.models import Log
 from ..common.helper import SerializerHelper
 from ..common.models import (
     AssessmentRootMixin,
@@ -34,6 +37,7 @@
     NonUniqueTagBase,
     get_private_data_storage,
 )
+from ..myuser.models import HAWCUser
 from . import constants, managers, tasks
 
 logger = logging.getLogger(__name__)
@@ -62,12 +66,76 @@ class LiteratureAssessment(models.Model):
         on_delete=models.CASCADE,
         related_name="literature_settings",
     )
+    conflict_resolution = models.BooleanField(
+        default=settings.HAWC_FEATURES.DEFAULT_LITERATURE_CONFLICT_RESOLUTION,
+        verbose_name="Conflict resolution required",
+        help_text="Enable conflict resolution for reference screening. If enabled, at least two reviewers must independently review and tag literature, and tag conflicts must be resolved before tags are applied to a reference. If disabled, tags are immediately applied to references.  We do not recommend changing this setting after screening has begun.",
+    )
     extraction_tag = models.ForeignKey(
         "lit.ReferenceFilterTag",
         blank=True,
         null=True,
         on_delete=models.SET_NULL,
-        help_text="All references or child references of this tag will be marked as ready for extraction.",
+        help_text="References tagged with this tag or its descendants will be available for data extraction and study quality/risk of bias evaluation.",
+    )
+    screening_instructions = models.TextField(
+        blank=True,
+        help_text="""Add instructions for screeners. This information will be shown on the
+        literature screening page and will publicly available, if the assessment is made public.""",
+    )
+    name_list_1 = models.CharField(
+        max_length=64,
+        verbose_name="Name List 1",
+        default="Positive",
+        help_text="Name for this list of keywords",
+    )
+    color_list_1 = models.CharField(
+        max_length=7,
+        verbose_name="Highlight Color 1",
+        default=ColorblindColors.BRIGHT[2],
+        help_text="Keywords in list 1 will be highlighted this color",
+    )
+    keyword_list_1 = models.TextField(
+        blank=True,
+        help_text="""Keywords to highlight in titles and abstracts on the reference tagging page.
+         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include
+         commas.""",
+    )
+    name_list_2 = models.CharField(
+        max_length=64,
+        verbose_name="Name List 2",
+        default="Negative",
+        help_text="Name for this list of keywords",
+    )
+    color_list_2 = models.CharField(
+        max_length=7,
+        verbose_name="Highlight Color 2",
+        default=ColorblindColors.BRIGHT[1],
+        help_text="Keywords in list 2 will be highlighted this color",
+    )
+    keyword_list_2 = models.TextField(
+        blank=True,
+        help_text="""Keywords to highlight in titles and abstracts on the reference tagging page.
+         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include
+         commas.""",
+    )
+    name_list_3 = models.CharField(
+        max_length=64,
+        verbose_name="Name List 3",
+        default="Additional",
+        help_text="Name for this list of keywords",
+    )
+    color_list_3 = models.CharField(
+        max_length=7,
+        verbose_name="Highlight Color 3",
+        default=ColorblindColors.BRIGHT[0],
+        help_text="Keywords in list 3 will be highlighted this color",
+    )
+    keyword_list_3 = models.TextField(
+        blank=True,
+        help_text="""Keywords to highlight in titles and abstracts on the reference tagging page.
+         Keywords are pipe-separated ("|") to allow for highlighting chemicals which may include
+         commas.""",
     )
     topic_tsne_data = models.FileField(
         blank=True,
@@ -175,6 +243,25 @@ def can_topic_model(self) -> bool:
     def can_request_refresh(self) -> bool:
         return self.can_topic_model and self.topic_tsne_refresh_requested is None
 
+    def get_keyword_data(self) -> dict:
+        return {
+            "set1": {
+                "name": self.name_list_1,
+                "color": self.color_list_1,
+                "keywords": [word.strip() for word in self.keyword_list_1.split("|") if word],
+            },
+            "set2": {
+                "name": self.name_list_2,
+                "color": self.color_list_2,
+                "keywords": [word.strip() for word in self.keyword_list_2.split("|") if word],
+            },
+            "set3": {
+                "name": self.name_list_3,
+                "color": self.color_list_3,
+                "keywords": [word.strip() for word in self.keyword_list_3.split("|") if word],
+            },
+        }
+
 
 class Search(models.Model):
     objects = managers.SearchManager()
@@ -735,24 +822,53 @@ def build_default(cls, assessment):
         exc.add_child(name="Tier III")
 
     @classmethod
-    def get_flattened_taglist(cls, tagslist, include_parent=True):
+    def get_flattened_taglist(cls, tags: dict):
         # expects tags dictionary dump_bulk format
-        lst = []
+        names = []
 
-        def appendChildren(obj, parents):
-            parents = parents + "|" if parents != "" else parents
-            txt = parents + obj["data"]["name"]
-            lst.append(txt)
+        def recurse(obj, parents):
+            txt = f'{parents}{"|" if parents else ""}{obj["data"]["name"]}'
+            names.append(txt)
             for child in obj.get("children", []):
-                appendChildren(child, txt)
+                recurse(child, txt)
 
-        if include_parent:
-            appendChildren(tagslist[0], "")
-        else:
-            for child in tagslist[0].get("children", []):
-                appendChildren(child, "")
+        for tag in tags[0].get("children", []):
+            recurse(tag, "")
+
+        return names
+
+    TreeDescendantType = dict[int, list[set[int]]]
+
+    @classmethod
+    def get_tree_descendants(cls, tags: dict) -> TreeDescendantType:
+        """
+        Returns a dictionary with each tag as the key, and its descendants as well as itself for
+        as a set of values. This is useful for checking if a parent tag should be considered true
+        given the status of a child tag.
+
+        Args:
+            tags (dict): a tag dictionary in dump_bulk formats
+
+        Returns:
+            TreeDescendantType: Returns output dictionary
+        """
+        descendants = {}
+
+        def recurse(tag: dict, parents: list[set]):
+            id = tag["id"]
+            for parent in parents:
+                parent.add(id)
+
+            descendants[id] = {id}
+            parents.append(descendants[id])
+
+            for child in tag.get("children", []):
+                recurse(child, copy(parents))
+
+        for tag in tags[0].get("children", []):
+            recurse(tag, [])
 
-        return lst
+        return descendants
 
 
 class ReferenceTags(ItemBase):
@@ -801,6 +917,69 @@ class Reference(models.Model):
 
     BREADCRUMB_PARENT = "assessment"
 
+    def update_tags(self, user, tag_pks: list[int]) -> bool:
+        """Update tags for user who requested this tags, and also potentially this reference.
+
+        This method was reviewed to try to reduce the number of db hits required, assuming that
+        the reference model has the required select and prefetch related, the tag comparisons
+        should not require any additional queries (but it may cause up to 5 db writes).
+
+        Args:
+            user: The user requesting the tag changes
+            tag_pks (list[int]): A list of tag IDs
+
+        Returns:
+            bool: If tags were also saved as consensus for Reference
+        """
+        # save user-level tags
+        user_tag, _ = self.user_tags.get_or_create(reference=self, user=user)
+        user_tag.is_resolved = False
+        user_tag.tags.set(tag_pks)
+        user_tag.save()
+
+        # determine if we should save the reference-level tags
+        update_reference_tags = False
+        conflict_resolution = self.assessment.literature_settings.conflict_resolution
+        if conflict_resolution:
+            unresolved_user_tags = self.user_tags.filter(is_resolved=False)
+            if unresolved_user_tags.count() >= 2:
+                tags = set(tag_pks)
+                if all(
+                    tags == {tag.id for tag in user_tag.tags.all()}
+                    for user_tag in unresolved_user_tags
+                ):
+                    update_reference_tags = True
+        else:
+            update_reference_tags = True
+
+        # if we should save reference-level tags, do so
+        if update_reference_tags:
+            self.user_tags.update(is_resolved=True)
+            self.tags.set(tag_pks)
+            self.last_updated = timezone.now()
+            self.save()
+
+        return conflict_resolution and update_reference_tags
+
+    def has_user_tag_conflicts(self):
+        return self.user_tags.filter(is_resolved=False).exists()
+
+    @transaction.atomic
+    def resolve_user_tag_conflicts(self, user_id: int, user_tag: "UserReferenceTag"):
+        tags = {tag.id for tag in user_tag.tags.all()}
+        log_message = (
+            f"Update lit.Reference tags #{self.id}: use lit.UserReferenceTag #{user_tag.id}: {tags}"
+        )
+        Log.objects.create(
+            assessment_id=self.assessment_id,
+            user_id=user_id,
+            message=log_message,
+            content_object=self,
+        )
+        self.tags.set(tags)
+        self.save()
+        self.user_tags.update(is_resolved=True)
+
     def get_absolute_url(self):
         return reverse("lit:ref_detail", args=(self.pk,))
 
@@ -825,10 +1004,10 @@ def to_dict(self):
             d[field] = getattr(self, field)
 
         d["url"] = self.get_absolute_url()
-        d["editTagUrl"] = reverse("lit:reference_tags_edit", kwargs={"pk": self.pk})
+        d["editTagUrl"] = reverse("lit:tag", kwargs={"pk": self.assessment_id}) + f"?id={self.pk}"
         d["editReferenceUrl"] = reverse("lit:ref_edit", kwargs={"pk": self.pk})
         d["deleteReferenceUrl"] = reverse("lit:ref_delete", kwargs={"pk": self.pk})
-
+        d["tagStatusUrl"] = reverse("lit:tag-status", kwargs={"pk": self.pk})
         d["identifiers"] = [ident.to_dict() for ident in self.identifiers.all()]
         d["searches"] = [search.to_dict() for search in self.searches.all()]
         d["study_short_citation"] = self.study.short_citation if d["has_study"] else None
@@ -1022,5 +1201,71 @@ def extract_dois(cls, qs, logger=None, full_text: bool = False):
                 f"{n-n_doi:8} references remaining without a DOI ({(n-n_doi)/n:.0%} missing DOI)"
             )
 
+    @classmethod
+    def annotate_tag_parents(cls, references: list, tags: models.QuerySet):
+        """Annotate tag parents for all tags and user tags.
+
+        Sets a new attribute (parents: list[Tag]) for each tag.
+
+        Args:
+            references (list): a list of references
+            tags (models.QuerySet): the full tag list for an assessment
+        """
+        tag_map = {tag.path: tag for tag in tags}
+
+        def _set_parents(tag):
+            tag.parents = []
+            current_path = tag.path
+            while True:
+                current_path = tag._get_parent_path_from_path(current_path)
+                if len(current_path) == 4:
+                    break
+                tag.parents.append(tag_map[current_path])
+
+        for reference in references:
+            for tag in reference.tags.all():
+                _set_parents(tag)
+            for user_tag in reference.user_tags.all():
+                for tag in user_tag.tags.all():
+                    _set_parents(tag)
+
+
+class UserReferenceTags(ItemBase):
+    objects = managers.UserReferenceTagsManager()
+
+    tag = models.ForeignKey(
+        ReferenceFilterTag, on_delete=models.CASCADE, related_name="user_references"
+    )
+    content_object = models.ForeignKey("UserReferenceTag", on_delete=models.CASCADE)
+
+
+class UserReferenceTag(models.Model):
+    user = models.ForeignKey(HAWCUser, on_delete=models.CASCADE, related_name="reference_tags")
+    reference = models.ForeignKey(Reference, on_delete=models.CASCADE, related_name="user_tags")
+    tags = managers.ReferenceFilterTagManager(through=UserReferenceTags, blank=True)
+    is_resolved = models.BooleanField(
+        default=False, help_text="User specific tag differences are resolved for this reference"
+    )
+    created = models.DateTimeField(auto_now_add=True)
+    last_updated = models.DateTimeField(auto_now=True)
+
+    @property
+    def assessment_id(self) -> int:
+        return self.reference.assessment_id
+
+    def consensus_diff(self):
+        # returns set operations done against consensus tags
+        consensus_tags = set(self.reference.tags.all())
+        self_tags = set(self.tags.all())
+        return dict(
+            intersection=self_tags.intersection(consensus_tags),
+            difference=self_tags.difference(consensus_tags),
+            reverse_difference=consensus_tags.difference(self_tags),
+        )
+
 
-reversion.register(Reference)
+reversion.register(LiteratureAssessment)
+reversion.register(Search)
+reversion.register(ReferenceFilterTag)
+reversion.register(Reference, follow=["tags"])
+reversion.register(UserReferenceTag, follow=["tags"])
diff --git a/hawc/apps/lit/serializers.py b/hawc/apps/lit/serializers.py
index bbee9ed9f4..212932088d 100644
--- a/hawc/apps/lit/serializers.py
+++ b/hawc/apps/lit/serializers.py
@@ -19,7 +19,7 @@
 from ..common.api import DynamicFieldsMixin
 from ..common.forms import ASSESSMENT_UNIQUE_MESSAGE
 from ..common.serializers import PydanticDrfSerializer, validate_jsonschema
-from . import constants, exports, forms, models, tasks
+from . import constants, forms, models, tasks
 
 logger = logging.getLogger(__name__)
 
@@ -293,9 +293,12 @@ def to_representation(self, instance):
 
         ret["has_study"] = instance.has_study
         ret["url"] = instance.get_absolute_url()
-        ret["editTagUrl"] = reverse("lit:reference_tags_edit", kwargs={"pk": instance.pk})
-        ret["editReferenceUrl"] = reverse("lit:ref_edit", kwargs={"pk": instance.pk})
-        ret["deleteReferenceUrl"] = reverse("lit:ref_delete", kwargs={"pk": instance.pk})
+        ret["editTagUrl"] = (
+            reverse("lit:tag", args=(instance.assessment_id,)) + f"?id={instance.pk}"
+        )
+        ret["editReferenceUrl"] = reverse("lit:ref_edit", args=(instance.pk,))
+        ret["deleteReferenceUrl"] = reverse("lit:ref_delete", args=(instance.pk,))
+        ret["tagStatusUrl"] = reverse("lit:tag-status", args=(instance.pk,))
 
         ret["identifiers"] = [ident.to_dict() for ident in instance.identifiers.all()]
         ret["searches"] = [search.to_dict() for search in instance.searches.all()]
@@ -409,23 +412,6 @@ def execute(self) -> ResultBase:
         return chain(t1, t2, t3)()
 
 
-class ReferenceTagExportSerializer(serializers.Serializer):
-    nested = serializers.ChoiceField(choices=[("t", "true"), ("f", "false")], default="t")
-    exporter = serializers.ChoiceField(
-        choices=[("base", "base"), ("table-builder", "table builder")], default="base"
-    )
-    _exporters = {
-        "base": exports.ReferenceFlatComplete,
-        "table-builder": exports.TableBuilderFormat,
-    }
-
-    def get_exporter(self):
-        return self._exporters[self.validated_data["exporter"]]
-
-    def include_descendants(self):
-        return self.validated_data["nested"] == "t"
-
-
 class FilterReferences(PydanticDrfSerializer):
     assessment_id: int  # no db validation required; done by viewset
     search: int = Field(None, alias="search_id")
diff --git a/hawc/apps/lit/templates/lit/_import_list.html b/hawc/apps/lit/templates/lit/_import_list.html
index 36c5e1cda5..6e9d24ac35 100644
--- a/hawc/apps/lit/templates/lit/_import_list.html
+++ b/hawc/apps/lit/templates/lit/_import_list.html
@@ -1,6 +1,6 @@
 <h3>Results from imports</h3>
 {% if obj_perms.edit %}
   <div class="well">
-    <a href="{% url 'lit:search_tags_edit' assessment.pk object.slug %}" class="btn btn-primary">Tag literature</a>
+    <a href="{% url 'lit:tag' assessment.pk %}?search={{object.pk}}" class="btn btn-primary">Tag literature</a>
   </div>
 {% endif %}
diff --git a/hawc/apps/lit/templates/lit/_nested_tag.html b/hawc/apps/lit/templates/lit/_nested_tag.html
new file mode 100644
index 0000000000..39c25d3b57
--- /dev/null
+++ b/hawc/apps/lit/templates/lit/_nested_tag.html
@@ -0,0 +1,1 @@
+<span class="refTag {{extra_classes}}">{% for ptag in tag.parents reversed %}{{ptag.name}} ➤ {% endfor %}{{tag.name}}</span>
diff --git a/hawc/apps/lit/templates/lit/_pubmedqueries_list.html b/hawc/apps/lit/templates/lit/_pubmedqueries_list.html
index ed098ab062..cf7a3e73d3 100644
--- a/hawc/apps/lit/templates/lit/_pubmedqueries_list.html
+++ b/hawc/apps/lit/templates/lit/_pubmedqueries_list.html
@@ -31,7 +31,7 @@ <h3>Results from queries</h3>
   <div class="well">
 
     {% if queries %}
-      <a href="{% url 'lit:search_tags_edit' assessment.pk object.slug %}" class="btn btn-primary">Tag literature</a>
+      <a href="{% url 'lit:tag' assessment.pk %}?search={{object.pk}}" class="btn btn-primary">Tag literature</a>
       <a id="run_search" href="{% url 'lit:search_query' assessment.pk object.slug %}" class="btn btn-light">Rerun search</a>
     {% else %}
       <a id="run_search" href="{% url 'lit:search_query' assessment.pk object.slug %}" class="btn btn-primary">Run search</a>
diff --git a/hawc/apps/lit/templates/lit/_reference_with_tags.html b/hawc/apps/lit/templates/lit/_reference_with_tags.html
new file mode 100644
index 0000000000..30bc42ca6b
--- /dev/null
+++ b/hawc/apps/lit/templates/lit/_reference_with_tags.html
@@ -0,0 +1,75 @@
+<div id="tagged-ref-{{ref.pk}}">
+    {% if show_actions %}
+    <div class="dropdown btn-group float-right">
+        <a class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown">Actions</a>
+        <div class="dropdown-menu dropdown-menu-right">
+            <a href="{% url 'lit:tag' assessment.pk%}?id={{ref.pk}}" class="dropdown-item" target="_blank" rel="noreferrer">Edit reference tags</a>
+            <a href="{% url 'lit:ref_edit' ref.pk%}" class="dropdown-item" target="_blank" rel="noreferrer">Edit reference</a>
+            <a href="{% url 'lit:tag-status' ref.pk%}" class="dropdown-item" target="_blank" rel="noreferrer">Tag status</a>
+        </div>
+    </div>
+    {% endif %}
+    <div>
+        <div class="ref_authors cursor-pointer ref_small mb-1">{{ref.authors}} {{ref.year}}</div>
+        <div class="ref_title cursor-pointer m-0">{{ref.title}}</div>
+        <div class="ref_small mb-1">
+            {% for id in ref.identifiers.all %}
+            <a href="{{id.get_url}}" class="ids badge badge-info">{{id.get_database_display}}</a>
+            {% endfor %}
+            <a href="{{ref.get_absolute_url}}" class="ids badge badge-warning">HAWC</a>
+        </div>
+        <p class="abstract abstract_collapsed cursor-pointer mb-1" title="Click to expand/collapse">{{ref.abstract|safe}}</p>
+    </div>
+    <div>
+        {% with ref.tags.all as tags %}{% if tags %}
+        <div class="consensus-tag mt-2">
+            <p class="my-2"><b>Consensus tags:</b></p>
+            {% for tag in tags %}{% include 'lit/_nested_tag.html' with tag=tag extra_classes='' %}{% endfor %}
+        </div>
+        {% endif %}{% endwith %}
+        {% for user_tag in ref.user_tags.all %}
+        <div class="user-tag box-shadow bg-white px-3 my-3 pb-2 pt-1">
+            <div>
+                {% if conflict_resolution %}
+                <form class="d-inline-block"
+                    hx-post="{% url 'lit:api:reference-resolve-conflict' ref.pk %}">
+                    <button class="approve btn btn-sm btn-success mr-2 align-center">
+                        <i class="fa fa-fw fa-check" aria-hidden="true"></i>&nbsp;Approve
+                    </button>
+                    <input name="user_tag_id" value={{user_tag.pk}} class="hidden">
+                </form>
+                {% endif %}
+                <div class="d-inline-block my-2">
+                    <b>{{user_tag.user.get_full_name}}</b> <i class="small">{{user_tag.last_updated}}</i>
+                </div>
+            </div>
+            {% if conflict_resolution %}
+                {% with user_tag.consensus_diff as diff %}
+                    {% for tag in ref.tags.all %}
+                        {% if tag in diff.intersection %}
+                            {% include 'lit/_nested_tag.html' with tag=tag extra_classes='' %}
+                        {% else %}
+                            {% include 'lit/_nested_tag.html' with tag=tag extra_classes='refUserTagRemove' %}
+                        {% endif %}
+                    {% endfor %}
+                    {% for tag in diff.difference %}
+                        {% include 'lit/_nested_tag.html' with tag=tag extra_classes='refUserTag' %}
+                    {% endfor %}
+                {% endwith %}
+            {% else %}
+                {% for tag in user_tag.tags.all %}
+                    {% include 'lit/_nested_tag.html' with tag=tag extra_classes='refUserTag' %}
+                {% endfor %}
+            {% endif %}
+        </div>
+        {% endfor %}
+    </div>
+</div>
+<script type="text/javascript">
+{% comment %}jQuery not available when script is inserted, but available when events are triggered{% endcomment %}
+document.querySelectorAll(
+    "#tagged-ref-{{ref.pk}} .abstract, #tagged-ref-{{ref.pk}} .ref_authors, #tagged-ref-{{ref.pk}} .ref_title"
+).forEach(
+    el => el.addEventListener('click', e => $(e.target).closest('#tagged-ref-{{ref.pk}}').find('.abstract').toggleClass("abstract_collapsed"))
+);
+</script>
diff --git a/hawc/apps/lit/templates/lit/conflict_resolution.html b/hawc/apps/lit/templates/lit/conflict_resolution.html
new file mode 100644
index 0000000000..0d830b5ab5
--- /dev/null
+++ b/hawc/apps/lit/templates/lit/conflict_resolution.html
@@ -0,0 +1,25 @@
+{% extends 'assessment-rooted.html' %}
+
+{% block content %}
+<h2>Resolve Tag Conflicts</h2>
+{% include 'common/filter_list.html' with plural_object_name='references' %}
+<ul class="list-group list-group-flush my-3">
+    {% for ref in object_list %}
+    <li class="list-group-item list-group-item-action conflict-reference-li {% if forloop.first %} pb-4 {% else %} py-4 {% endif %}"
+        id="ref-form-{{ref.pk}}" hx-target="#ref-form-{{ref.pk}}" hx-swap="delete swap:1s">
+        {% include 'lit/_reference_with_tags.html' with conflict_resolution=True show_actions=True %}
+    </li>
+    {% empty %}
+    <li class="list-group-item">
+        <div class="alert alert-success">
+            <i class='fa fa-fw fa-check mr-1'></i>&nbsp;Conflict resolution complete! No conflicts to resolve.
+        </div>
+    </li>
+    {% endfor %}
+</ul>
+{% include "includes/paginator.html" with plural_object_name="conflicts" %}
+{% endblock content %}
+
+{% block extrajs %}
+{% include "common/helptext_popup_js.html" %}
+{% endblock extrajs %}
diff --git a/hawc/apps/lit/templates/lit/overview.html b/hawc/apps/lit/templates/lit/overview.html
index ed4d42a4f7..8f14890b5b 100644
--- a/hawc/apps/lit/templates/lit/overview.html
+++ b/hawc/apps/lit/templates/lit/overview.html
@@ -1,85 +1,157 @@
 {% extends 'assessment-rooted.html' %}
 
 {% block content %}
-  <h2 class="d-inline-block">References for {{assessment}}</h2>
+  <h2 class="d-inline-block">Literature Review</h2>
   {% if obj_perms.edit %}
   <div class="dropdown btn-group float-right">
     <a class="btn btn-primary dropdown-toggle" data-toggle="dropdown">Actions</a>
     <div class="dropdown-menu dropdown-menu-right">
-      <span class="dropdown-header">Search/import editing</span>
+      {% if obj_perms.edit_assessment %}
+        <span class="dropdown-header">Literature Review</span>
+        <a class="dropdown-item" href="{{assessment.literature_settings.get_update_url}}">Update settings</a>
+        <a class="dropdown-item" href="{% url 'lit:tags_update' assessment.pk %}">Update tags</a>
+        <div class="dropdown-divider"></div>
+      {% endif %}
+      <span class="dropdown-header">Searches and Imports</span>
       <a class="dropdown-item" href="{% url 'lit:search_new' assessment.pk %}">New search</a>
       <a class="dropdown-item" href="{% url 'lit:import_new' assessment.pk %}">New import by ID</a>
       {% if allow_ris %}<a class="dropdown-item" href="{% url 'lit:import_ris_new' assessment.pk %}">New import from RIS</a>{% endif %}
       <a class="dropdown-item" href="{% url 'lit:copy_search' assessment.pk %}">Copy search from existing</a>
       <div class="dropdown-divider"></div>
-      <span class="dropdown-header">Reference editing</span>
+      <span class="dropdown-header">References</span>
       <a class="dropdown-item" href="{% url 'study:new_ref' assessment.pk %}">Add new reference (manually)</a>
-      <a class="dropdown-item" href="{% url 'lit:search_tags_edit' assessment.pk manual_import.slug %}">Tag manually-added references</a>
-      <a class="dropdown-item" href="{% url 'lit:tag_untagged' assessment.pk %}">Tag untagged references</a>
-      <a class="dropdown-item" href="{% url 'lit:bulk_tag' assessment.pk %}">Bulk tag references</a>
-      {% if obj_perms.edit_assessment %}
-        <div class="dropdown-divider"></div>
-        <span class="dropdown-header">Assessment-level editing</span>
-        <a class="dropdown-item" href="{% url 'lit:tags_update' assessment.pk %}">Update literature tags</a>
-        <a class="dropdown-item" href="{% url 'lit:ref_upload' assessment.pk %}">Upload full-text URLs</a>
+      {% if assessment.literature_settings.conflict_resolution %}
+        <a class="dropdown-item" href="{% url 'lit:tag-conflicts' assessment.pk %}">Resolve tag conflicts</a>
       {% endif %}
+      <a class="dropdown-item" href="{% url 'lit:bulk_tag' assessment.pk %}">Bulk tag references</a>
+      <a class="dropdown-item" href="{% url 'lit:tag' assessment.pk %}?search={{manual_import.pk}}">Tag manually added references</a>
+      <a class="dropdown-item" href="{% url 'lit:tag' assessment.pk %}?untagged=on">Tag untagged references</a>
+      <a class="dropdown-item" href="{% url 'lit:ref_upload' assessment.pk %}">Upload full text URLs</a>
       <div class="dropdown-divider"></div>
       <span class="dropdown-header">Exports</span>
-      <a class="dropdown-item" href="{% url 'lit:api:assessment-references-download' assessment.pk %}?format=xlsx">Download all references</a>
+      <a class="dropdown-item" href="{% url 'lit:api:assessment-reference-export' assessment.pk %}?format=xlsx">Download all references</a>
     </div>
   </div>
 {% endif %}
-
   <div class="row no-gutter">
-    <div class="col-md-6">
-      <h3>Literature summary</h3>
-      <table class="table table-sm table-hover">
-        <tbody>
-          <tr>
-            <th>Total references</th>
-            <td title="If a reference is found in both a search and import, it is included in the search-counts only">
-              {{overview.total_references}}
-              ({{overview.total_searched}} from searches,
-              {{overview.total_imported}} from imports)</td>
-          </tr>
-          <tr>
-            <th>Tagged</th>
-            <td>{{overview.total_tagged}}</td>
-          </tr>
-          <tr>
-            <th>Untagged</th>
-            <td>{{overview.total_untagged}}</td>
-          </tr>
-          {% if overview.total_references >= 10 %}
-          <tr>
-            <th colspan="2">
-              References by year<br/>
-            <div id="referenceYearHistogram" style="height: 200px"></div>
-          </tr>
+    <div class="col-md-8">
+      <div class="d-flex row justify-content-around">
+      {% comment %} row #1 {% endcomment %}
+        <a class="lit-review-box box-shadow with-icon clickable w-30" href="{% url 'lit:ref_search' assessment.pk %}">
+          <div>
+            <p class="h3 m-0 mt-1">{{overview.total_references}}</p>
+            <p class="small">total references</p>
+          </div>
+          <div class="lit-icon">
+            <i class="fa fa-file-text icon-bg" style="color: white; background-color: #162e51;"></i>
+          </div>
+        </a>
+        <a class="lit-review-box box-shadow with-icon clickable w-30"href="{% url 'lit:ref_list' assessment.pk %}">
+          <div>
+            <p class="h3 m-0 mt-1">{{overview.total_tagged}}</p>
+            <p class="small">tagged</p>
+          </div>
+          <div class="lit-icon">
+            <i class="fa fa-tags icon-bg" style="color: white; background-color: #162e51;"></i>
+          </div>
+        </a>
+        <a class="lit-review-box box-shadow with-icon clickable w-30" href="{% url 'lit:ref_search' assessment.pk %}?tags=untagged">
+          <div>
+            <p class="h3 m-0 mt-1">{{overview.total_untagged}}</p>
+            <p class="small">untagged</p>
+          </div>
+          <div class="lit-icon">
+            <span class="fa-stack fa-1x icon-bg" style="background-color: #162e51;">
+              <i class="fa fa-tag fa-stack-1x" style="font-size: 0.65em;"></i>
+              <i class="fa fa-ban fa-stack-1x" style="font-size: 1.5em;"></i>
+            </span>
+          </div>
+        </a>
+        {% comment %} row #2 {% endcomment %}
+        <div class="lit-review-box box-shadow w-30 justify-content-between py-4" style="height: 235px">
+          {% if obj_perms.edit %}
+            {% if assessment.literature_settings.conflict_resolution %}
+            <a class="btn btn-primary my-1 py-1" href="{% url 'lit:tag' assessment.pk %}?needs_tagging=on&my_tags=untagged">Tag</a>
+            {% else %}
+            <a class="btn btn-primary my-1 py-1" href="{% url 'lit:tag' assessment.pk %}?tags=untagged">Tag</a>
+            {% endif %}
           {% endif %}
-        </tbody>
-      </table>
-
-      <div class="well">
-        <a class="btn btn-primary" href="{% url 'lit:ref_list' assessment.pk %}">View by tag</a>
-        <a class="btn btn-primary" href="{% url 'lit:ref_visual' assessment.pk %}">Visualization</a>
-        {% if user.is_beta_tester %}
-          {% if can_topic_model %}
-          <a class="betatester btn btn-dark" href="{% url 'lit:topic_model' assessment.pk %}">Topic model</a>
+          <a class="btn btn-primary my-1 py-1" href="{% url 'lit:ref_list' assessment.pk %}">Browse</a>
+          <a class="btn btn-primary my-1 py-1" href="{% url 'lit:ref_search' assessment.pk %}">Search</a>
+          <a class="btn btn-primary my-1 py-1" href="{% url 'lit:ref_visual' assessment.pk %}">Visualize</a>
+          {% if user.is_beta_tester and can_topic_model %}
+          <a class="btn btn-dark betatester mt-1 py-1" href="{% url 'lit:topic_model' assessment.pk %}">Topic model</a>
           {% endif %}
+        </div>
+        {% if overview.total_references > 10 %}
+        <div class="lit-review-box box-shadow {% if assessment.literature_settings.conflict_resolution and obj_perms.edit %}w-30{% else %}w-60{% endif %}" style="height: 235px">
+          <p>References by year:</p>
+          <div id="referenceYearHistogram" style="height: 175px; width: 100%;">
+          </div>
+        </div>
+        {% endif %}
+        {% if assessment.literature_settings.conflict_resolution and obj_perms.edit %}
+        <div class="w-30">
+          <a class="lit-review-box box-shadow with-icon clickable w-100" href="{% url 'lit:tag-conflicts' assessment.pk %}">
+            <div>
+              <p class="h3 m-0 mt-1">{{overview.conflicts}}</p>
+              <p class="small">with tag conflicts</p>
+            </div>
+            <div class="lit-icon">
+              <div class="icon-bg" style="background-color: #ff9393;">&#8800;</div>
+            </div>
+          </a>
+          <a class="lit-review-box box-shadow with-icon clickable w-100" href="{% url 'lit:tag' assessment.pk %}?needs_tagging=on">
+            <div>
+              <p class="h3 m-0 mt-1">{{overview.needs_tagging}}</p>
+              <p class="small">need{{ overview.needs_tagging|pluralize:"s," }} further tagging</p>
+            </div>
+            <div class="lit-icon">
+              <i class="fa fa-tags icon-bg" style="color: #162e51; border: 0.15rem dotted #162e51; background-color: white;"></i>
+            </div>
+          </a>
+        </div>
+        {% comment %} row #3 {% endcomment %}
+        <a class="lit-review-box box-shadow with-icon clickable w-30" href="{% url 'lit:user-tag-list' assessment.pk %}">
+          <div>
+            <p><b>{{overview.total_reviews}}</b> reviews done by <b>{{overview.total_users}}</b> user{{ overview.total_users|pluralize }}</p>
+          </div>
+          <div class="lit-icon">
+            <i class="fa fa-users icon-bg"></i>
+          </div>
+        </a>
+        {% if overview.my_reviews > 0 %}
+          <a class="lit-review-box box-shadow with-icon clickable w-30" href="{% url 'lit:tag' assessment.pk %}?anything_tagged_me=on">
+            <div>
+              <p class="h3 m-0 mt-1">{{overview.my_reviews}}</p>
+              <p class="small">reviews done by you</p>
+            </div>
+            <div class="lit-icon">
+              <i class="fa fa-user icon-bg"></i>
+            </div>
+          </a>
+        {% endif %}
         {% endif %}
-        <a class="btn btn-primary" href="{% url 'lit:ref_search' assessment.pk %}">Find a reference</a>
       </div>
     </div>
-    <div class="col-md-6">
-      <h3>Literature tags
-        {% if obj_perms.edit_assessment %}
-        {% endif %}
-      </h3>
-      <div id="tags"></div>
+    <div class="col-md-4">
+      <div class="lit-review-box box-shadow" style="width: 100%;">
+        <p class="h4 m-0">Tagtree
+          {% include "common/helptext_popup.html" with text="Tag, plus count of references with this tag applied" %}
+        </p>
+        <div id="tags"></div>
+      </div>
     </div>
   </div>
 
+  {% if obj_perms.edit and overview.conflicts %}
+    <div class="alert alert-danger mt-3">
+      <button type="button" class="close" data-dismiss="alert">&times;</button>
+      <b>References have tag conflicts!</b><br>
+      <p>{{ overview.conflicts }} reference{{ overview.conflicts|pluralize }} {{ overview.conflicts|pluralize:"is,are" }} ready for conflict resolution. <a href="{% url 'lit:tag-conflicts' assessment.pk %}">Resolve tag conflicts</a></p>
+    </div>
+  {% endif %}
+
   {% if assessment.enable_data_extraction and need_import_count > 0 %}
     <div class="alert alert-warning mt-3">
       <button type="button" class="close" data-dismiss="alert">&times;</button>
@@ -103,7 +175,11 @@ <h3>Manually added references</h3>
 
   window.app.startup("litStartup", function(lit){
     let tagtree = new lit.TagTree(config.tags[0], config.assessment_id, null);
-    tagtree.render(document.getElementById('tags'))
+    tagtree.add_references(config.references);
+    tagtree.render(
+      document.getElementById('tags'),
+      {handleTagClick: (x) => {window.location.href = x.get_list_link()}, showReferenceCount: true, style: {height: "25rem"}}
+    )
   });
 
   const histo = document.getElementById("referenceYearHistogram");
@@ -111,4 +187,5 @@ <h3>Manually added references</h3>
     window.app.renderPlotlyFromApi(histo, config.referenceYearHistogramUrl);
   }
 </script>
+{% include "common/helptext_popup_js.html" %}
 {% endblock extrajs %}
diff --git a/hawc/apps/lit/templates/lit/reference_search.html b/hawc/apps/lit/templates/lit/reference_search.html
index 610fa1e206..3db29197a9 100644
--- a/hawc/apps/lit/templates/lit/reference_search.html
+++ b/hawc/apps/lit/templates/lit/reference_search.html
@@ -9,4 +9,5 @@ <h2>References ({{page_obj.paginator.count}} found)</h2>
 
 {% block extrajs %}
 {{ config|json_script:"config" }}
+{% include "common/helptext_popup_js.html" %}
 {% endblock extrajs %}
diff --git a/hawc/apps/lit/templates/lit/reference_tag.html b/hawc/apps/lit/templates/lit/reference_tag.html
index 28b248d12d..d300984ceb 100644
--- a/hawc/apps/lit/templates/lit/reference_tag.html
+++ b/hawc/apps/lit/templates/lit/reference_tag.html
@@ -1,9 +1,14 @@
 {% extends 'assessment-rooted.html' %}
 
 {% block content %}
+  <div class="mb-2">
+    {% include 'common/filter_list.html' with plural_object_name='references' %}
+  </div>
   <div id="main"></div>
+  {% include "includes/paginator.html" with plural_object_name="references" %}
 {% endblock %}
 
 {% block extrajs %}
   {{ config|json_script:"config" }}
+  {% include "common/helptext_popup_js.html" %}
 {% endblock extrajs %}
diff --git a/hawc/apps/lit/templates/lit/reference_tag_status.html b/hawc/apps/lit/templates/lit/reference_tag_status.html
new file mode 100644
index 0000000000..f0c6c60183
--- /dev/null
+++ b/hawc/apps/lit/templates/lit/reference_tag_status.html
@@ -0,0 +1,6 @@
+{% extends 'assessment-rooted.html' %}
+
+
+{% block content %}
+{% include 'lit/_reference_with_tags.html' with ref=object %}
+{% endblock %}
diff --git a/hawc/apps/lit/templates/lit/reference_user_tags.html b/hawc/apps/lit/templates/lit/reference_user_tags.html
new file mode 100644
index 0000000000..bd7a3acc92
--- /dev/null
+++ b/hawc/apps/lit/templates/lit/reference_user_tags.html
@@ -0,0 +1,25 @@
+{% extends 'assessment-rooted.html' %}
+
+{% block content %}
+<h2>Reference User Tags</h2>
+
+{% include 'common/filter_list.html' with plural_object_name='references' %}
+<ul class="list-group list-group-flush my-3">
+    {% for ref in object_list %}
+    <li class="list-group-item list-group-item-action conflict-reference-li {% if forloop.first %} pb-4 {% else %} py-4 {% endif %}">
+        {% include 'lit/_reference_with_tags.html' with conflict_resolution=False show_actions=False %}
+    </li>
+    {% empty %}
+    <li class="list-group-item">
+        <div class="alert alert-success">
+            <i class='fa fa-fw fa-check mr-1'></i>&nbsp;No reference user tags found.
+        </div>
+    </li>
+    {% endfor %}
+</ul>
+{% include "includes/paginator.html" with plural_object_name="references" %}
+{% endblock content %}
+
+{% block extrajs %}
+{% include "common/helptext_popup_js.html" %}
+{% endblock extrajs %}
diff --git a/hawc/apps/lit/templates/lit/search_detail.html b/hawc/apps/lit/templates/lit/search_detail.html
index d29ef7001b..73707a9669 100644
--- a/hawc/apps/lit/templates/lit/search_detail.html
+++ b/hawc/apps/lit/templates/lit/search_detail.html
@@ -12,7 +12,7 @@ <h2 class="d-inline-block">{{object}}</h2>
             <a class="dropdown-item" href="{% url 'lit:search_delete' object.assessment.pk object.slug %}">Delete {{object.get_search_type_display|lower}}</a>
           {% endif %}
         {% endif %}
-        <a class="dropdown-item" href="{% url 'lit:api:search-references' object.id %}?format=xlsx">Download to Excel</a>
+        <a class="dropdown-item" href="{% url 'lit:api:assessment-reference-export' assessment.id %}?format=xlsx&searches={{object.id}}">Download to Excel</a>
       </div>
     </div>
   {% endif %}
@@ -63,7 +63,7 @@ <h3>Literature Tagging Statistics</h3>
         <th>Reference details</th>
         <td>
           <a class="btn btn-primary" href="{% url 'lit:search_tags' assessment.pk object.slug %}">View by tag</a>
-          <a class="btn btn-primary" href="{% url 'lit:search_tags_visual' assessment.pk object.slug %}">Visualization</a>
+          <a class="btn btn-primary" href="{% url 'lit:search_tags_visual' assessment.pk object.slug %}">Visualize</a>
         </td>
       </tr>
     </tbody>
diff --git a/hawc/apps/lit/templates/lit/tags_update.html b/hawc/apps/lit/templates/lit/tags_update.html
index 674191b304..a299cfffe7 100644
--- a/hawc/apps/lit/templates/lit/tags_update.html
+++ b/hawc/apps/lit/templates/lit/tags_update.html
@@ -3,11 +3,7 @@
 {% block content %}
 <div id='main'></div>
 <div class="well">
-  <a class='btn btn-primary'
-     href="{% url 'lit:tags_copy' assessment.pk %}">
-      Copy tags from existing assessment</a>
-  <a class='btn btn-primary float-right' href="{{ lit_assesment_update_url }}">
-    Modify extraction tag</a>
+  <a class='btn btn-primary' href="{% url 'lit:tags_copy' assessment.pk %}">Copy tags from existing assessment</a>
 </div>
 {% endblock %}
 
diff --git a/hawc/apps/lit/templates/lit/topic_model.html b/hawc/apps/lit/templates/lit/topic_model.html
index a7778990ed..b08e029981 100644
--- a/hawc/apps/lit/templates/lit/topic_model.html
+++ b/hawc/apps/lit/templates/lit/topic_model.html
@@ -1,12 +1,16 @@
 {% extends 'assessment-rooted.html' %}
 
 {% block content %}
-{% if obj_perms.edit and object.can_request_refresh %}
-<a id="refresh-topic-model" class="float-right btn btn-primary" href=".">
-  {% if object.has_topic_model %}Request model refresh{% else %}Build model{% endif %}
-</a>
-{% endif %}
-<h2>Topic model.</h2>
+<div class="d-flex justify-content-between">
+  <h2>Topic model.</h2>
+  <div>
+    {% if obj_perms.edit and object.can_request_refresh %}
+    <a id="refresh-topic-model" class="float-right btn btn-primary" href=".">
+      {% if object.has_topic_model %}Request model refresh{% else %}Build model{% endif %}
+    </a>
+    {% endif %}
+  </div>
+</div>
 
 {% if obj_perms.edit %}
   {% if object.topic_tsne_refresh_requested %}
diff --git a/hawc/apps/lit/urls.py b/hawc/apps/lit/urls.py
index 60dd22f244..e9be4d9c98 100644
--- a/hawc/apps/lit/urls.py
+++ b/hawc/apps/lit/urls.py
@@ -35,15 +35,14 @@
     path("reference/<int:pk>/update/", views.RefEdit.as_view(), name="ref_edit"),
     path("reference/<int:pk>/delete/", views.RefDelete.as_view(), name="ref_delete"),
     path(
-        "reference/<int:pk>/tag/",
-        views.TagByReference.as_view(),
-        name="reference_tags_edit",
+        "reference/<int:pk>/tag-status/",
+        views.ReferenceTagStatus.as_view(),
+        name="tag-status",
     ),
-    path("tag/<int:pk>/tag/", views.TagByTag.as_view(), name="references_tags_edit"),
     path(
-        "assessment/<int:pk>/tag/untagged/",
-        views.TagByUntagged.as_view(),
-        name="tag_untagged",
+        "assessment/<int:pk>/tag/",
+        views.TagReferences.as_view(),
+        name="tag",
     ),
     path(
         "assessment/<int:pk>/tag/bulk/",
@@ -123,11 +122,6 @@
         name="import_ris_new",
     ),
     # Edit tags
-    path(
-        "assessment/<int:pk>/search/<slug:slug>/tag/",
-        views.TagBySearch.as_view(),
-        name="search_tags_edit",
-    ),
     path(
         "assessment/<int:pk>/search/<slug:slug>/tags/",
         views.SearchRefList.as_view(),
@@ -143,5 +137,15 @@
         views.RISExportInstructions.as_view(),
         name="ris_export_instructions",
     ),
+    path(
+        "assessment/<int:pk>/reference-tag-conflicts/",
+        views.ConflictResolution.as_view(),
+        name="tag-conflicts",
+    ),
+    path(
+        "assessment/<int:pk>/user-tags/",
+        views.UserTagList.as_view(),
+        name="user-tag-list",
+    ),
     path("api/", include((router.urls, "api"))),
 ]
diff --git a/hawc/apps/lit/views.py b/hawc/apps/lit/views.py
index 466db2fc0d..598e56b15d 100644
--- a/hawc/apps/lit/views.py
+++ b/hawc/apps/lit/views.py
@@ -3,6 +3,7 @@
 from django.conf import settings
 from django.core.exceptions import PermissionDenied
 from django.db import transaction
+from django.db.models import Count, Q
 from django.forms.models import model_to_dict
 from django.http import HttpResponseRedirect
 from django.middleware.csrf import get_token
@@ -52,6 +53,13 @@ def get_queryset(self):
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         context["overview"] = models.Reference.objects.get_overview_details(self.assessment)
+        context["overview"]["my_reviews"] = (
+            models.Reference.objects.filter(assessment=self.assessment)
+            .filter(user_tags__user=self.request.user)
+            .count()
+            if self.request.user.is_authenticated
+            else 0
+        )
         context["manual_import"] = models.Search.objects.get_manually_added(self.assessment)
         if context["obj_perms"]["edit"]:
             context["need_import_count"] = models.Reference.objects.get_references_ready_for_import(
@@ -60,6 +68,7 @@ def get_context_data(self, **kwargs):
         context["can_topic_model"] = self.assessment.literature_settings.can_topic_model()
         context["config"] = {
             "tags": models.ReferenceFilterTag.get_all_tags(self.assessment.id),
+            "references": models.Reference.objects.tag_pairs(self.assessment.references.all()),
             "assessment_id": self.assessment.id,
             "referenceYearHistogramUrl": reverse(
                 "lit:api:assessment-reference-year-histogram", args=(self.assessment.id,)
@@ -241,107 +250,246 @@ def get(self, request, *args, **kwargs):
         return HttpResponseRedirect(self.object.get_absolute_url())
 
 
-class TagReferences(BaseDetail):
-    """
-    Abstract base-class to tag references, using various methods to get instance.
-    """
-
-    model = Assessment
-    form_class = forms.TagReferenceForm
+class TagReferences(BaseFilterList):
     template_name = "lit/reference_tag.html"
+    parent_model = Assessment
+    model = models.Reference
     assessment_permission = AssessmentViewPermissions.TEAM_MEMBER
+    paginate_by = 100
 
-    def get_ref_qs_filters(self) -> dict:
-        raise NotImplementedError("Subclass requires implementation")
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .select_related("study")
+            .prefetch_related("searches", "identifiers", "tags")
+        )
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         context.update(
-            breadcrumbs=lit_overview_crumbs(self.request.user, self.assessment, "CHANGE"),
+            breadcrumbs=lit_overview_crumbs(self.request.user, self.assessment, "Update tags"),
         )
         return context
 
-    def get_app_config(self, context) -> WebappConfig:
-        if hasattr(self, "qs_reference"):
-            refs = self.qs_reference
+    def get_filterset_class(self):
+        conflict_resolution = self.assessment.literature_settings.conflict_resolution
+        if conflict_resolution:
+            return dynamic_filterset(
+                filterset.ReferenceFilterSet,
+                fields=[
+                    "title_abstract",
+                    "needs_tagging",
+                    "partially_tagged",
+                    "search",
+                    "id",
+                    "tags",
+                    "include_descendants",
+                    "anything_tagged",
+                    "order_by",
+                    "my_tags",
+                    "include_mytag_descendants",
+                    "anything_tagged_me",
+                ],
+                grid_layout={
+                    "rows": [
+                        {
+                            "columns": [
+                                {"width": 5, "extra_css": "pl-4"},
+                                {
+                                    "width": 3,
+                                    "extra_css": "px-3 pt-3 d-flex flex-column",
+                                    "rows": [
+                                        {"columns": [{"width": 12}]},
+                                        {"columns": [{"width": 12}]},
+                                    ],
+                                },
+                                {"width": 4, "extra_css": "pr-4"},
+                            ]
+                        },
+                        {
+                            "columns": [
+                                {
+                                    "width": 6,
+                                    "rows": [
+                                        {
+                                            "columns": [
+                                                {"width": 12, "extra_css": "px-4 py-2"},
+                                                {"width": 12, "extra_css": "px-4 pt-2"},
+                                                {"width": 7, "extra_css": "pl-4 pb-2"},
+                                                {"width": 5, "extra_css": "pr-4 pb-2"},
+                                            ]
+                                        }
+                                    ],
+                                },
+                                {
+                                    "width": 6,
+                                    "rows": [
+                                        {
+                                            "columns": [
+                                                {"width": 12, "extra_css": "px-4 py-2"},
+                                                {"width": 12, "extra_css": "px-4 pt-2"},
+                                                {"width": 7, "extra_css": "px-4 pb-2"},
+                                                {"width": 5, "extra_css": "px-4 pb-2"},
+                                            ]
+                                        }
+                                    ],
+                                },
+                            ]
+                        },
+                    ]
+                },
+            )
         else:
-            refs = refs = models.Reference.objects.filter(**self.get_ref_qs_filters()).distinct()
-        refs = refs.prefetch_related("searches", "identifiers", "tags")
+            return dynamic_filterset(
+                filterset.ReferenceFilterSet,
+                fields=[
+                    "title_abstract",
+                    "search",
+                    "id",
+                    "order_by",
+                    "tags",
+                    "include_descendants",
+                    "anything_tagged",
+                ],
+                grid_layout={
+                    "rows": [
+                        {
+                            "columns": [
+                                {"width": 6, "extra_css": "px-3"},
+                                {"width": 6, "extra_css": "px-3"},
+                            ]
+                        },
+                        {
+                            "columns": [
+                                {
+                                    "width": 6,
+                                    "rows": [
+                                        {
+                                            "columns": [
+                                                {"width": 12, "extra_css": "px-3"},
+                                                {"width": 12, "extra_css": "px-3"},
+                                            ]
+                                        }
+                                    ],
+                                },
+                                {
+                                    "width": 6,
+                                    "rows": [
+                                        {
+                                            "columns": [
+                                                {"width": 12, "extra_css": "px-3"},
+                                                {"width": 6, "extra_css": "pl-3"},
+                                                {"width": 6, "extra_css": "pr-3"},
+                                            ]
+                                        }
+                                    ],
+                                },
+                            ]
+                        },
+                    ]
+                },
+            )
+
+    def get_app_config(self, context) -> WebappConfig:
+        references = [ref.to_dict() for ref in context["object_list"]]
+        ref_tags = context["object_list"].unresolved_user_tags(user_id=self.request.user.id)
+        for reference in references:
+            reference["user_tags"] = ref_tags.get(reference["pk"])
         return WebappConfig(
             app="litStartup",
             page="startupTagReferences",
             data=dict(
+                conflict_resolution=self.assessment.literature_settings.conflict_resolution,
+                keywords=self.assessment.literature_settings.get_keyword_data(),
+                instructions=self.assessment.literature_settings.screening_instructions,
                 tags=models.ReferenceFilterTag.get_all_tags(self.assessment.id),
-                refs=[ref.to_dict() for ref in refs],
+                refs=references,
                 csrf=get_token(self.request),
             ),
         )
 
 
-class TagBySearch(TagReferences):
-    """
-    Edit tags for a single Search.
-    """
-
-    model = models.Search
-
-    def get_ref_qs_filters(self):
-        return dict(searches=self.object)
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context["breadcrumbs"][3] = Breadcrumb.from_object(self.object)
-        context["breadcrumbs"].append(Breadcrumb(name="Update tags"))
-        return context
-
-
-class TagByReference(TagReferences):
-    """
-    Edit tags for on a single reference.
-    """
-
+class ConflictResolution(BaseFilterList):
+    template_name = "lit/conflict_resolution.html"
+    parent_model = Assessment
     model = models.Reference
+    assessment_permission = AssessmentViewPermissions.TEAM_MEMBER
+    filterset_class = dynamic_filterset(
+        filterset.ReferenceFilterSet,
+        fields=[
+            "id",
+            "title_abstract",
+            "tags",
+            "include_descendants",
+            "anything_tagged",
+            "my_tags",
+            "include_mytag_descendants",
+            "anything_tagged_me",
+        ],
+        grid_layout={
+            "rows": [
+                {
+                    "columns": [
+                        {"width": 6, "extra_css": "px-4 py-2"},
+                        {"width": 6, "extra_css": "px-4 py-2"},
+                    ]
+                },
+                {
+                    "columns": [
+                        {
+                            "width": 6,
+                            "rows": [
+                                {
+                                    "columns": [
+                                        {"width": 12, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                    ]
+                                }
+                            ],
+                        },
+                        {
+                            "width": 6,
+                            "rows": [
+                                {
+                                    "columns": [
+                                        {"width": 12, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                    ]
+                                }
+                            ],
+                        },
+                    ]
+                },
+            ]
+        },
+    )
+    paginate_by = 50
 
-    def get_ref_qs_filters(self):
-        return dict(pk=self.object.pk)
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context["breadcrumbs"][3] = Breadcrumb.from_object(self.object)
-        context["breadcrumbs"].append(Breadcrumb(name="Update tags"))
-        return context
-
-
-class TagByTag(TagReferences):
-    """
-    Tag references with a specific tag.
-    """
-
-    model = models.ReferenceFilterTag
-
-    def get_ref_qs_filters(self):
-        return dict(tags=self.object.pk)
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context["breadcrumbs"][3] = Breadcrumb(name=f'Update "{self.object.name}" tags')
-        return context
-
-
-class TagByUntagged(TagReferences):
-    """
-    View to tag all untagged references for an assessment.
-    """
-
-    model = Assessment
-
-    def get_ref_qs_filters(self):
-        return dict(tags=self.object.pk)
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .annotate(
+                n_unapplied_reviews=Count("user_tags__user", filter=Q(user_tags__is_resolved=False))
+            )
+            .filter(n_unapplied_reviews__gt=1)
+            .order_by("-last_updated")
+            .prefetch_related("identifiers", "tags", "user_tags__user", "user_tags__tags")
+        )
 
     def get_context_data(self, **kwargs):
-        self.qs_reference = self.assessment.references.all().untagged()
         context = super().get_context_data(**kwargs)
-        context["breadcrumbs"][3] = Breadcrumb(name="Tag untagged references")
+        tags = models.ReferenceFilterTag.get_assessment_qs(self.assessment.id)
+        context.update(
+            tags=tags,
+            breadcrumbs=lit_overview_crumbs(
+                self.request.user, self.assessment, "Resolve Tag Conflicts"
+            ),
+        )
+        models.Reference.annotate_tag_parents(context["object_list"], tags)
         return context
 
 
@@ -443,25 +591,58 @@ class RefFilterList(BaseFilterList):
     model = models.Reference
     filterset_class = dynamic_filterset(
         filterset.ReferenceFilterSet,
+        fields=[
+            "id",
+            "db_id",
+            "search",
+            "year",
+            "title_abstract",
+            "authors",
+            "journal",
+            "order_by",
+            "paginate_by",
+            "tags",
+            "include_descendants",
+            "anything_tagged",
+        ],
         grid_layout={
             "rows": [
-                {"columns": [{"width": 3}, {"width": 3}, {"width": 3}, {"width": 3}]},
                 {
                     "columns": [
+                        {"width": 3, "extra_css": "px-4"},
+                        {"width": 3, "extra_css": "px-4"},
+                        {"width": 3, "extra_css": "px-4"},
+                        {"width": 3, "extra_css": "px-4"},
+                    ]
+                },
+                {
+                    "columns": [
+                        {
+                            "width": 6,
+                            "rows": [
+                                {
+                                    "columns": [
+                                        {"width": 12, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                    ]
+                                }
+                            ],
+                        },
                         {
-                            "width": 5,
+                            "width": 6,
                             "rows": [
                                 {
                                     "columns": [
-                                        {"width": 12},
-                                        {"width": 12},
-                                        {"width": 6},
-                                        {"width": 6},
+                                        {"width": 12, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
+                                        {"width": 6, "extra_css": "px-4"},
                                     ]
                                 }
                             ],
                         },
-                        {"width": 7},
                     ]
                 },
             ]
@@ -587,6 +768,49 @@ def get_app_config(self, context) -> WebappConfig:
         return _get_ref_app_startup(self, context)
 
 
+class ReferenceTagStatus(BaseDetail):
+    template_name = "lit/reference_tag_status.html"
+    model = models.Reference
+    assessment_permission = AssessmentViewPermissions.TEAM_MEMBER
+
+    def get_queryset(self):
+        return (
+            super()
+            .get_queryset()
+            .select_related("assessment")
+            .prefetch_related("identifiers", "tags", "user_tags__tags", "user_tags__user")
+        )
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["breadcrumbs"] = lit_overview_crumbs(
+            self.request.user, self.assessment, "Tag status"
+        )
+        context["breadcrumbs"].insert(3, Breadcrumb.from_object(self.object))
+        tags = models.ReferenceFilterTag.get_assessment_qs(self.assessment.id)
+        models.Reference.annotate_tag_parents([self.object], tags)
+        return context
+
+
+class UserTagList(ConflictResolution):
+    template_name = "lit/reference_user_tags.html"
+
+    def get_queryset(self):
+        return (
+            self.filterset.qs.filter(user_tags__gt=0)
+            .order_by("-last_updated")
+            .prefetch_related("identifiers", "tags", "user_tags__user", "user_tags__tags")
+        )
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["breadcrumbs"] = lit_overview_crumbs(
+            self.request.user, self.assessment, "Reference User Tags"
+        )
+        context["header"] = "Reference User Tags"
+        return context
+
+
 class RefEdit(BaseUpdate):
     success_message = "Reference updated."
     model = models.Reference
@@ -709,7 +933,7 @@ def get_context_data(self, **kwargs):
         return context
 
     def get_success_url(self):
-        return reverse_lazy("lit:tags_update", args=(self.assessment.id,))
+        return reverse_lazy("lit:overview", args=(self.assessment.id,))
 
 
 class TagsCopy(BaseUpdate):
@@ -761,3 +985,17 @@ def get_app_config(self, context) -> WebappConfig:
             page="startupBulkTagReferences",
             data={"assessment_id": self.assessment.id, "csrf": get_token(self.request)},
         )
+
+
+class ReferenceTagHistory(BaseDetail):
+    template_name = "lit/reference_tag_history.html"
+    model = models.Reference
+    assessment_permission = AssessmentViewPermissions.TEAM_MEMBER
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["breadcrumbs"] = lit_overview_crumbs(
+            self.request.user, self.assessment, "Tag history"
+        )
+        context["breadcrumbs"].insert(3, Breadcrumb.from_object(self.object))
+        return context
diff --git a/hawc/constants.py b/hawc/constants.py
index 4e889d7afd..87443e957d 100644
--- a/hawc/constants.py
+++ b/hawc/constants.py
@@ -14,9 +14,26 @@ class FeatureFlags(BaseModel):
     THIS_IS_AN_EXAMPLE: bool = True
     ENABLE_ECO: bool = False
     FIPS_MODE: bool = False
+    DEFAULT_LITERATURE_CONFLICT_RESOLUTION: bool = False
     ALLOW_RIS_IMPORTS: bool = True
     ANONYMOUS_ACCOUNT_CREATION: bool = True
 
     @classmethod
     def from_env(cls, variable) -> "FeatureFlags":
         return cls.parse_obj(json.loads(os.environ.get(variable, "{}")))
+
+
+class ColorblindColors:
+    """
+    A collection of colorblind friendly color pallettes for use in HAWC, as needed.
+    Originally gathered from: https://personal.sron.nl/~pault/
+    """
+
+    # fmt: off
+    BRIGHT = ["#4477AA", "#EE6677", "#228833", "#CCBB44", "#66CCEE", "#AA3377", "#BBBBBB"]
+    VIBRANT = ["#EE7733", "#0077BB", "#33BBEE", "#EE3377", "#CC3311", "#009988", "#BBBBBB"]
+    MUTED = [
+        "#CC6677", "#332288", "#DDCC77", "#117733", "#88CCEE",
+        "#882255", "#44AA99", "#999933", "#DDDDDD"
+    ]
+    # fmt: on
diff --git a/hawc/static/css/hawc.css b/hawc/static/css/hawc.css
index 7cc57e270a..b76b05829e 100644
--- a/hawc/static/css/hawc.css
+++ b/hawc/static/css/hawc.css
@@ -35,7 +35,7 @@
     }
 }
 .sidebar a {
-    color: #f8f9fa!important;
+    color: #f8f9fa !important;
 }
 .sidebar .nav {
     font-weight: bold;
@@ -63,6 +63,14 @@ tfoot > tr > td {
     overflow-y: auto;
     resize:vertical;
 }
+.clickable:hover {
+    cursor: pointer;
+    background-color: #f3f3f3;
+    text-decoration: none;
+}
+.box-shadow {
+    box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
+}
 .showOptsCaret:hover .optsCaret {
     opacity: 1;
     -webkit-transition: all 0.5s ease-in-out;
@@ -267,69 +275,262 @@ div.smart-tag.active {
 }
 
 /* LITERATURE MODULE */
-.litSavedIcon {
-    color: #5b5bef;
-    font-weight: bold;
-    margin-left: 15px;
-    padding: 5px 10px;
+.lit-review-box {
+    border-radius: 5px;
+    padding: 16px;
+    margin-bottom: 24px;
+    min-height: 6.5rem;
+    display: flex;
+    justify-content: center;
+    flex-direction:column;
+    color: black !important;
+    height: fit-content;
+}
+.lit-review-box.with-icon {
+    justify-content: space-between;
+    flex-direction: row;
+}
+.lit-review-box.with-icon div {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+}
+.lit-icon {
+    padding-left: 16px;
+}
+.lit-icon .icon-bg {
+    display: flex !important;
+    justify-content: center;
+    background: #aacdec;
+    border-radius: 8px;
+    height: 55px;
+    width: 55px;
+    color: white;
+    font-size: 2em;
+    align-items:center;
+}
+.small-tag-icon {
+    height: 20px;
+    width: 20px;
     border-radius: 4px;
-    border: 1px solid #8080c0;
-    box-shadow: 0px 0px 10px 3px #5b5bef;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    font-size: 0.75rem;
+}
+.small-tag-icon.consensus {
+    color: white;
+    background-color: rgb(22, 46, 81);
+}
+.small-tag-icon.user {
+    color: rgb(22, 46, 81);
+    border: 2px dotted rgb(22, 46, 81);
+    background-color: white;
+}
+#refFilter h4,
+#fullRefList p {
+    overflow: hidden;
+    word-wrap: normal;
+}
+#refFilter {
+    transition-property: all !important;
+    transition: width 250ms ease-out;
+    max-width: 22%;
+    width: 22%;
+    padding: 0rem 1rem;
+}
+#refFilter.slideAway {
+    transition: all 250ms ease-in;
+    max-width: 0%;
+    padding: 0px 0px 0px 0px;
+    overflow: hidden;
+}
+#refFilter.slideAway div p {
+    /* prevent squashing on collapse */
+    max-height: 26px;
+}
+#taggingCol {
+    transition: width 250ms ease-out;
+    width: 53%;
+    padding: 0rem 1rem;
+}
+#taggingCol.slideAway {
+    transition: width 250ms ease-in;
+    width: 75%;
+}
+#filter-btn {
+    transition: transform 250ms ease-in-out;
+}
+#filter-btn.slideAway>#caret-left {
+    transform: scaleX(-1);
+    transition: transform 250ms ease-in-out;
+}
+#litTagtree {
+    background-color: #e0f0ff;
+    border-radius: 7px;
+    height: 50rem;
+}
+.abstracts {
+    font-size: 1.15rem;
+    margin-top: 0.25rem;
+    background-color: #f5f5f5;
+    border-radius: 6px;
 }
 .ref_small {
     font-size: 0.8465em;
     display: block;
+    margin: 0;
 }
 .ref_title {
     font-weight: bold;
-    margin: 0.375em 0;
+    margin: 0;
+    font-size: 1.35rem;
+    color: #1b1b1b;
 }
 .refTag {
     color: #ffffff;
-    background-color: maroon;
-    border-radius: 8px;
-    font-weight: bold;
-    padding: 3px 5px 3px 5px;
-    margin: 0 3px 3px 0;
+    background-color: #162e51;
+    border-radius: 10px;
+    border: 0.15rem dotted transparent;
+    padding: 4px 7px 4px 7px;
+    margin: 0 5px 7px 0;
     display: inline-block;
 }
+.refUserTag {
+    color: #162e51;
+    background-color: #ffffff;
+    border: 0.15rem dotted #162e51;
+}
 .referenceTag{
     text-align: left;
     white-space: pre-wrap;
 }
-.refTagEditing {
-    cursor: pointer;
-}
 .abstract_label {
     font-weight: bold;
 }
-.nestedTag {
-    border: 2px solid transparent;
+ .nestedTag {
     cursor: pointer;
+    border: 1px solid transparent;
+}
+
+.nestedTag > div.tagHoverAdd {
+    display: inline-block;
+    position: relative;
+    background: linear-gradient(0deg, #484848, #484848) no-repeat right bottom / 0 var(--bg-h);
+    transition: background-size 200ms;
+    --bg-h: 2px;
+    text-decoration: none;
+    padding-left: 5px;
+    padding-right: 15px;
+    border: 3px solid transparent;
 }
-.nestedTag:hover {
-    border: 2px solid #ababab;
-    background-color: #8ee8ff;
+
+.nestedTag > div.tagHoverAdd:where(:hover, :focus-visible) {
+    background-size: 100% var(--bg-h);
+    background-position-x: left;
+}
+
+.nestedTag>div.tagHoverAdd::before {
+    color: #484848;
+    content: "\f067";
+    font-family: FontAwesome;
+    font-size: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: absolute;
+    top: -1px;
+    right: -1px;
+    width: 25px;
+    height: 110%;
+    transform: scale(1, 0);
+    transform-origin: bottom;
+    transition: all 75ms ease-in 125ms;
+}
+
+.nestedTag>div.tagHoverAdd:hover::before {
+    transform: scale(1, 1);
+}
+.tagHover {
+    padding-left: 5px;
+    display: inline-block;
+    position: relative;
+    border: 2px solid transparent;
+    text-decoration: none;
 }
-.nestedTag.selected {
-    color: #0044cc;
+.tagHover:hover, .tagSelected {
     font-weight: bold;
-    border: 2px solid #ababab;
-    background-color: gold;
+}
+
+.tagSelected {
+    text-decoration: underline;
 }
 .reference {
-    padding-left: 0.5rem;
+    border-bottom: 3px solid lightgray;
+    padding: 0.3rem 0rem 0.3rem 0.5rem;
 }
 .reference:hover {
     cursor: pointer;
+    background-color: #d6d6d6;
 }
 .reference.selected {
     color: #ffffff;
-    background-color: maroon;
+    background-color: #7f7f7f;
 }
-.reference:hover {
+
+.hawc-mk {
+    background-color: transparent;
+    color: inherit;
+    text-decoration: none;
+    overflow-wrap: break-word;
+    word-break: break-word;
+    word-wrap: break-word;
+}
+
+.conflict-reference-li {
+    border-bottom: 5px solid #b0b0b0 !important;
+}
+
+.abstract_collapsed {
+    overflow: hidden;
+    white-space: nowrap !important;
+    width: 800px !important;
+    height: 30px;
+}
+
+.abstract {
+    text-overflow: ellipsis;
+    display: block;
+    white-space: unset;
+    width: auto;
+}
+
+.list-item-clicked,
+.list-item-clicked:hover {
+    background-color: #eeeeee;
+}
+
+.refTagDiff,
+.refTagSame {
+    color: #ffffff;
+    border-radius: 8px;
+    font-weight: bold;
+    margin: 4px 2px 4px 8px;
+    display: inline-block;
+}
+
+.refUserTagRemove {
+    color: rgb(128,0,0);
+    background-color: #ffffff;
+    border: 0.15rem dotted rgb(128,0,0);
+    text-decoration: line-through rgba(128,0,0,0.5);
+}
+
+.refTagSame {
     color: #ffffff;
-    background-color: #0044cc;
+    font-size: 0.85rem;
+    padding: 5px 7px 5px 7px;
+    background-color: #7e7e7e;
 }
 
 /* preview modal icon */
@@ -483,6 +684,15 @@ table.bordered td {
     transition: opacity 1s ease-out;
 }
 
+/* fade in/out */
+.fade-in-out {
+    animation: fadeInOutKeyFrame 2s linear;
+}
+@keyframes fadeInOutKeyFrame {
+    0%,100% { opacity: 0 }
+    20%,80% { opacity: 1 }
+}
+
 /* quill */
 .ql-editor {
     background-color: white !important;
@@ -495,7 +705,12 @@ table.bordered td {
 .cursor-pointer {
     cursor: pointer;
 }
-
+.w-30 {
+    width: 30%!important;
+}
+.w-60 {
+    width: 60%!important;
+}
 .betatester {
     background-color: #ec744c;
     border-color: #ec744c;
diff --git a/hawc/templates/crispy_forms/layout/field.html b/hawc/templates/crispy_forms/layout/field.html
new file mode 100644
index 0000000000..f2ac75dec4
--- /dev/null
+++ b/hawc/templates/crispy_forms/layout/field.html
@@ -0,0 +1,87 @@
+{% load crispy_forms_field %}
+
+{% if field.is_hidden %}
+    {{ field }}
+{% else %}
+    {% if field|is_checkbox %}
+        <div class="form-group{% if 'form-horizontal' in form_class %} row{% endif %}">
+        {% if label_class %}
+            <div class="{% for offset in bootstrap_checkbox_offsets %}{{ offset }} {% endfor %}{{ field_class }}">
+        {% endif %}
+    {% endif %}
+    <{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="{% if not field|is_checkbox %}form-group{% if 'form-horizontal' in form_class %} row{% endif %}{% else %}{%if use_custom_control%}{% if tag != 'td' %}custom-control {%endif%} custom-checkbox{% else %}form-check{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
+        {% if field.label and not field|is_checkbox and form_show_labels %}
+        {# not field|is_radioselect in row below can be removed once Django 3.2 is no longer supported #}
+        <label {% if field.id_for_label and not field|is_radioselect %}for="{{ field.id_for_label }}" {% endif %}class="{% if 'form-horizontal' in form_class %}col-form-label {% endif %}{{ label_class }}{% if field.field.required %} requiredField{% endif %}">
+                {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
+                {% if field.field.hover_help %}
+                    {% include 'common/helptext_popup.html' with text=field.field.help_text %}
+                {% endif %}
+            </label>
+        {% endif %}
+
+        {% if field|is_checkboxselectmultiple %}
+            {% include 'bootstrap4/layout/checkboxselectmultiple.html' %}
+        {% endif %}
+
+        {% if field|is_radioselect %}
+            {% include 'bootstrap4/layout/radioselect.html' %}
+        {% endif %}
+
+        {% if not field|is_checkboxselectmultiple and not field|is_radioselect %}
+            {% if field|is_checkbox and form_show_labels %}
+                {%if use_custom_control%}
+                    {% if field.errors %}
+                        {% crispy_field field 'class' 'custom-control-input is-invalid' %}
+                    {% else %}
+                        {% crispy_field field 'class' 'custom-control-input' %}
+                    {% endif %}
+                {% else %}
+                    {% if field.errors %}
+                        {% crispy_field field 'class' 'form-check-input is-invalid' %}
+                    {% else %}
+                        {% crispy_field field 'class' 'form-check-input' %}
+                    {% endif %}
+                {% endif %}
+                <label for="{{ field.id_for_label }}" class="{%if use_custom_control%}custom-control-label{% else %}form-check-label{% endif %}{% if field.field.required %} requiredField{% endif %}">
+                    {{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
+                    {% if field.field.hover_help %}
+                    {% include 'common/helptext_popup.html' with text=field.field.help_text %}
+                    {% endif %}
+                </label>
+                {% include 'crispy_forms/layout/help_text_and_errors.html' %}
+            {% elif field|is_file and use_custom_control  %}
+                {% include 'bootstrap4/layout/field_file.html' %}
+            {% else %}
+                <div{% if field_class %} class="{{ field_class }}"{% endif %}>
+                    {% if field|is_select and use_custom_control %}
+                        {% if field.errors %}
+                            {% crispy_field field 'class' 'custom-select is-invalid' %}
+                        {% else %}
+                            {% crispy_field field 'class' 'custom-select' %}
+                        {% endif %}
+                    {% elif field|is_file %}
+                        {% if field.errors %}
+                            {% crispy_field field 'class' 'form-control-file is-invalid' %}
+                        {% else %}
+                            {% crispy_field field 'class' 'form-control-file' %}
+                        {% endif %}
+                    {% else %}
+                        {% if field.errors %}
+                            {% crispy_field field 'class' 'form-control is-invalid' %}
+                        {% else %}
+                            {% crispy_field field 'class' 'form-control' %}
+                        {% endif %}
+                    {% endif %}
+                    {% include 'crispy_forms/layout/help_text_and_errors.html' %}
+                </div>
+            {% endif %}
+        {% endif %}
+    </{% if tag %}{{ tag }}{% else %}div{% endif %}>
+    {% if field|is_checkbox %}
+        {% if label_class %}
+            </div>
+        {% endif %}
+        </div>
+    {% endif %}
+{% endif %}
diff --git a/hawc/templates/crispy_forms/layout/help_text_and_errors.html b/hawc/templates/crispy_forms/layout/help_text_and_errors.html
new file mode 100644
index 0000000000..bdd41f8f88
--- /dev/null
+++ b/hawc/templates/crispy_forms/layout/help_text_and_errors.html
@@ -0,0 +1,13 @@
+{% if help_text_inline and not error_text_inline %}
+    {% include 'bootstrap4/layout/help_text.html' %}
+{% endif %}
+
+{% if error_text_inline %}
+    {% include 'bootstrap4/layout/field_errors.html' %}
+{% else %}
+    {% include 'bootstrap4/layout/field_errors_block.html' %}
+{% endif %}
+
+{% if not help_text_inline and not field.field.hover_help %}
+    {% include 'bootstrap4/layout/help_text.html' %}
+{% endif %}


Test patch:
diff --git a/tests/client/test_client.py b/tests/client/test_client.py
index 4ddd59f0e2..92f311b255 100644
--- a/tests/client/test_client.py
+++ b/tests/client/test_client.py
@@ -4,7 +4,7 @@
 from django.test import LiveServerTestCase, TestCase
 
 from hawc.apps.animal.models import Experiment
-from hawc.apps.assessment.models import Assessment, DoseUnits, Strain
+from hawc.apps.assessment.models import DoseUnits, Strain
 from hawc.apps.epi.models import (
     ComparisonSet,
     Criteria,
@@ -291,8 +291,7 @@ def test_epi_endpoints(self):
 
     def test_epi_metadata(self):
         client = HawcClient(self.live_server_url)
-        assessment = Assessment.objects.first()
-        response = client.epi.metadata(assessment.id)
+        response = client.epi.metadata(self.db_keys.assessment_client)
         assert isinstance(response, dict)
 
     def test_epi_create(self):
@@ -607,6 +606,12 @@ def test_lit_references(self):
         response = client.lit.references(self.db_keys.assessment_client)
         assert isinstance(response, pd.DataFrame)
 
+    def test_lit_reference_user_tags(self):
+        client = HawcClient(self.live_server_url)
+        client.authenticate("pm@hawcproject.org", "pw")
+        response = client.lit.reference_user_tags(self.db_keys.assessment_client)
+        assert isinstance(response, pd.DataFrame)
+
     def test_lit_reference(self):
         # get request
         client = HawcClient(self.live_server_url)
diff --git a/tests/conftest.py b/tests/conftest.py
index d40ae3bc21..17bb2ce176 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,6 +25,7 @@ def __init__(self):
         self.assessment_final = 2
         self.assessment_client = 3
         self.assessment_keys = [1, 2]
+        self.assessment_conflict_resolution = 4
 
         self.dataset_final = 2
         self.dataset_working = 1
@@ -36,6 +37,10 @@ def __init__(self):
         self.reference_linked = 1
         self.reference_unlinked = 3
 
+        self.reference_untagged = 9
+        self.reference_tag_conflict = 10
+        self.reference_tagged = 11
+
         self.animal_group_working = 1
         self.endpoint_working = 1
 
diff --git a/tests/data/api/api-lit-assessment-reference-export-user-tags.json b/tests/data/api/api-lit-assessment-reference-export-user-tags.json
new file mode 100644
index 0000000000..b7470b9e29
--- /dev/null
+++ b/tests/data/api/api-lit-assessment-reference-export-user-tags.json
@@ -0,0 +1,122 @@
+[
+  {
+    "Abstract": "Consuming small-sized fish species whole, and bones of large fish could contribute significantly to reducing the level of micronutrient and protein malnutrition. These fish products are more affordable and could therefore meet the needs of poor, vulnerable groups, particularly in rural and urban areas where limited economic resources prevent dietary diversity. The objectives of the study were to produce fish powder from dried edible byproducts from fish processing factories, an underutilized fish species, burrito and to determine the physical, micromineral, macronutrient and microbiological quality of the dried fish powder. Edible fish processing byproducts and an underutilized fish, burrito (Brachydeuterus auritus) were cleaned thoroughly and dried with a Council for Scientific and Industrial Research-Food Research Institute (CSIR-FRI) gas-fuelled oven at 55\u00b0C for 8\u00a0h or until dried. The dried products were milled into powder, and packaged into polythene bags. Proximate analysis of the fish powder was done Official Methods of Analysis (AOAC) methods. Minerals and heavy metals in the fish powder were determined by atomic absorption spectrophotometry (AAS). Microbiological quality was determined by Nordic Committee on Food Analysis Method (NMLK) methods. Tuna trimmings contained 80.71\u00a0g/100\u00a0g protein, whereas burrito contained 70.40\u00a0g/100\u00a0g protein. Concentrations of cadmium, arsenic, and mercury varied from <1.00 to 1\u00a0mg/kg. Lead was found at 0.04\u00a0mg/100\u00a0g in tuna frames and gills only. All fish byproducts contained high levels of iron, for example, trimmings contained 16.58\u00a0mg/100\u00a0g, whereas tuna frames and gills also contained 16.82 and 19.54\u00a0mg/100\u00a0g, respectively. Burrito contained 8.92\u00a0mg/100\u00a0g. Zinc levels also ranged from 0.41\u00a0mg/100\u00a0g in tuna trimmings to 1.88\u00a0mg/100\u00a0g in tuna gills. The powdered samples according to the standards set by Ghana Standard Authority, were acceptable. Consuming small- sized fish species whole, and bones of large fish could contribute significantly to reducing the level of micronutrient and protein malnutrition. These are more affordable and could therefore meet the needs of poor, vulnerable groups.",
+    "Authors": "Abbey L, Glover-Amengor M, Atikpo MO, Atter A, Toppe J",
+    "Authors short": "Abbey L et al.",
+    "Citation": "Abbey L et al. 2017",
+    "Created": 1666904581424,
+    "DOI": "10.1002/fsn3.402",
+    "Exclusion": false,
+    "Exclusion|Tier I": false,
+    "Exclusion|Tier II": false,
+    "Exclusion|Tier III": false,
+    "Full Citation": "Abbey L, Glover-Amengor M, Atikpo MO, Atter A, Toppe J. Nutrient content of fish powder from low value fish and fish byproducts. Food Sci Nutr 2017; 5 (3):374-379. ",
+    "Full text URL": "",
+    "HAWC ID": 10,
+    "HERO ID": null,
+    "Inclusion": true,
+    "Inclusion|Animal Study": true,
+    "Inclusion|Human Study": false,
+    "Inclusion|Mechanistic Study": false,
+    "Journal": "Food Sci Nutr 2017; 5 (3):374-379",
+    "Last updated": 1666906828379,
+    "PubMed ID": 28572920,
+    "Title": "Nutrient content of fish powder from low value fish and fish byproducts.",
+    "User Tag Author": "Team Member",
+    "User Tag Author ID": 3,
+    "User Tag ID": 2,
+    "User Tags Last Updated": 1666904942014,
+    "User Tags Resolved": false,
+    "Year": 2017
+  },
+  {
+    "Abstract": "Consuming small-sized fish species whole, and bones of large fish could contribute significantly to reducing the level of micronutrient and protein malnutrition. These fish products are more affordable and could therefore meet the needs of poor, vulnerable groups, particularly in rural and urban areas where limited economic resources prevent dietary diversity. The objectives of the study were to produce fish powder from dried edible byproducts from fish processing factories, an underutilized fish species, burrito and to determine the physical, micromineral, macronutrient and microbiological quality of the dried fish powder. Edible fish processing byproducts and an underutilized fish, burrito (Brachydeuterus auritus) were cleaned thoroughly and dried with a Council for Scientific and Industrial Research-Food Research Institute (CSIR-FRI) gas-fuelled oven at 55\u00b0C for 8\u00a0h or until dried. The dried products were milled into powder, and packaged into polythene bags. Proximate analysis of the fish powder was done Official Methods of Analysis (AOAC) methods. Minerals and heavy metals in the fish powder were determined by atomic absorption spectrophotometry (AAS). Microbiological quality was determined by Nordic Committee on Food Analysis Method (NMLK) methods. Tuna trimmings contained 80.71\u00a0g/100\u00a0g protein, whereas burrito contained 70.40\u00a0g/100\u00a0g protein. Concentrations of cadmium, arsenic, and mercury varied from <1.00 to 1\u00a0mg/kg. Lead was found at 0.04\u00a0mg/100\u00a0g in tuna frames and gills only. All fish byproducts contained high levels of iron, for example, trimmings contained 16.58\u00a0mg/100\u00a0g, whereas tuna frames and gills also contained 16.82 and 19.54\u00a0mg/100\u00a0g, respectively. Burrito contained 8.92\u00a0mg/100\u00a0g. Zinc levels also ranged from 0.41\u00a0mg/100\u00a0g in tuna trimmings to 1.88\u00a0mg/100\u00a0g in tuna gills. The powdered samples according to the standards set by Ghana Standard Authority, were acceptable. Consuming small- sized fish species whole, and bones of large fish could contribute significantly to reducing the level of micronutrient and protein malnutrition. These are more affordable and could therefore meet the needs of poor, vulnerable groups.",
+    "Authors": "Abbey L, Glover-Amengor M, Atikpo MO, Atter A, Toppe J",
+    "Authors short": "Abbey L et al.",
+    "Citation": "Abbey L et al. 2017",
+    "Created": 1666904581424,
+    "DOI": "10.1002/fsn3.402",
+    "Exclusion": true,
+    "Exclusion|Tier I": true,
+    "Exclusion|Tier II": false,
+    "Exclusion|Tier III": false,
+    "Full Citation": "Abbey L, Glover-Amengor M, Atikpo MO, Atter A, Toppe J. Nutrient content of fish powder from low value fish and fish byproducts. Food Sci Nutr 2017; 5 (3):374-379. ",
+    "Full text URL": "",
+    "HAWC ID": 10,
+    "HERO ID": null,
+    "Inclusion": false,
+    "Inclusion|Animal Study": false,
+    "Inclusion|Human Study": false,
+    "Inclusion|Mechanistic Study": false,
+    "Journal": "Food Sci Nutr 2017; 5 (3):374-379",
+    "Last updated": 1666906828379,
+    "PubMed ID": 28572920,
+    "Title": "Nutrient content of fish powder from low value fish and fish byproducts.",
+    "User Tag Author": "Project Manager",
+    "User Tag Author ID": 2,
+    "User Tag ID": 5,
+    "User Tags Last Updated": 1666906828356,
+    "User Tags Resolved": false,
+    "Year": 2017
+  },
+  {
+    "Abstract": "ETHNOPHARMACOLOGICAL RELEVANCE: Aloysia polystachya (Griseb) Moldenke (Verbenaceae), popularly known as \"burrito\", is a South American species widely prescribed by local Brazilian healers for the treatment of cardiovascular diseases. However, its antihypertensive and cardioprotective effects are still unknown.AIM: To evaluate the role of the ethanol-soluble fraction of A. polystachya leaves (ESAP) against hypertension in spontaneously hypertensive rats (SHRs), as well as its safety, morphoanatomical and phytochemical aspects.MATERIALS AND METHODS: First, the leaves and stems of A. polystachya were analyzed by optical and scanning electron microscopy in order to provide anatomical data for quality control. Then, ESAP was obtained and its chemical profile was analyzed by LC-DAD-MS. In addition, the cytotoxic and acute toxicity potential of ESAP were evaluated in six cell lines and in female Wistar rats, respectively. Next, female spontaneously hypertensive rats (SHRs) received ESAP (30, 100, 300\u00a0mg/kg), hydrochlorothiazide (25\u00a0mg/kg), or vehicle once daily for 28 days. Weekly kidney function was monitored by analyzing urinary parameters. At the end of the 28-day treatment, the electrocardiographic profile, blood pressure, and renal and mesenteric vascular reactivity were evaluated. Relative organ (heart, kidney, and liver) weights and biochemical parameters were also evaluated. Finally, the heart, kidneys, and aorta were collected for determination of the tissue redox state, cardiac morphometry, and histopathological analysis.RESULTS: The chemical profile of ESAP was composed by organic acids, a nucleoside, methoxylated flavones and glycosylated compounds including phenolic acids, phenylpropanoids, iridoids and monoterpenes. No signs of toxicity were observed in all cell's lines nor in female Wistar rats submitted to this trial. All SHRs from the negative control group presented a reduction in renal function, alterations in the renal and mesenteric vascular reactivity, and electrocardiographic and morphometric changes typical of ventricular hypertrophy. Oral prolonged ESAP-administration in SHRs was able to reverse renal, electrocardiographic and hemodynamic changes induced by hypertension. Moreover, ESAP-treatment was able to modulate the vascular and renal arterial reactivity and tissue redox state. The aforementioned data were accompanied by reduction of cardiac hypertrophy.CONCLUSION: In this study, we present important anatomical and phytochemical data that contributed to the correct identification and quality control of A. polystachya. In addition, we have shown that ESAP is safe after acute administration and present significant cardioprotective effects (at 30, 100, and 300\u00a0mg/kg doses) in SHRs after prolonged treatment.",
+    "Authors": "Marques AAM, Loren\u00e7one BR, Rom\u00e3o PVM, Guarnier LP, Palozi RAC, Moreno KGT, Tirloni CAS, Dos Santos AC, Souza RIC, Klider LM, Louren\u00e7o ELB, Tolouei SEL, Budel JM, Khan SI, Silva DB, Gasparotto Junior A",
+    "Authors short": "Marques AAM et al.",
+    "Citation": "Marques AAM et al. 2021",
+    "Created": 1666904581546,
+    "DOI": "10.1016/j.jep.2021.114077",
+    "Exclusion": false,
+    "Exclusion|Tier I": false,
+    "Exclusion|Tier II": false,
+    "Exclusion|Tier III": false,
+    "Full Citation": "Marques AAM, Loren\u00e7one BR, Rom\u00e3o PVM, Guarnier LP, Palozi RAC, Moreno KGT, Tirloni CAS, Dos Santos AC, Souza RIC, Klider LM, Louren\u00e7o ELB, Tolouei SEL, Budel JM, Khan SI, Silva DB, Gasparotto Junior A. Ethnopharmacological investigation of the cardiovascular effects of the ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke leaves in spontaneously hypertensive rats. J Ethnopharmacol 2021; 274 ():114077. ",
+    "Full text URL": "",
+    "HAWC ID": 11,
+    "HERO ID": null,
+    "Inclusion": true,
+    "Inclusion|Animal Study": false,
+    "Inclusion|Human Study": true,
+    "Inclusion|Mechanistic Study": false,
+    "Journal": "J Ethnopharmacol 2021; 274 ():114077",
+    "Last updated": 1666906611995,
+    "PubMed ID": 33789140,
+    "Title": "Ethnopharmacological investigation of the cardiovascular effects of the ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke leaves in spontaneously hypertensive rats.",
+    "User Tag Author": "Team Member",
+    "User Tag Author ID": 3,
+    "User Tag ID": 1,
+    "User Tags Last Updated": 1666904870420,
+    "User Tags Resolved": true,
+    "Year": 2021
+  },
+  {
+    "Abstract": "ETHNOPHARMACOLOGICAL RELEVANCE: Aloysia polystachya (Griseb) Moldenke (Verbenaceae), popularly known as \"burrito\", is a South American species widely prescribed by local Brazilian healers for the treatment of cardiovascular diseases. However, its antihypertensive and cardioprotective effects are still unknown.AIM: To evaluate the role of the ethanol-soluble fraction of A. polystachya leaves (ESAP) against hypertension in spontaneously hypertensive rats (SHRs), as well as its safety, morphoanatomical and phytochemical aspects.MATERIALS AND METHODS: First, the leaves and stems of A. polystachya were analyzed by optical and scanning electron microscopy in order to provide anatomical data for quality control. Then, ESAP was obtained and its chemical profile was analyzed by LC-DAD-MS. In addition, the cytotoxic and acute toxicity potential of ESAP were evaluated in six cell lines and in female Wistar rats, respectively. Next, female spontaneously hypertensive rats (SHRs) received ESAP (30, 100, 300\u00a0mg/kg), hydrochlorothiazide (25\u00a0mg/kg), or vehicle once daily for 28 days. Weekly kidney function was monitored by analyzing urinary parameters. At the end of the 28-day treatment, the electrocardiographic profile, blood pressure, and renal and mesenteric vascular reactivity were evaluated. Relative organ (heart, kidney, and liver) weights and biochemical parameters were also evaluated. Finally, the heart, kidneys, and aorta were collected for determination of the tissue redox state, cardiac morphometry, and histopathological analysis.RESULTS: The chemical profile of ESAP was composed by organic acids, a nucleoside, methoxylated flavones and glycosylated compounds including phenolic acids, phenylpropanoids, iridoids and monoterpenes. No signs of toxicity were observed in all cell's lines nor in female Wistar rats submitted to this trial. All SHRs from the negative control group presented a reduction in renal function, alterations in the renal and mesenteric vascular reactivity, and electrocardiographic and morphometric changes typical of ventricular hypertrophy. Oral prolonged ESAP-administration in SHRs was able to reverse renal, electrocardiographic and hemodynamic changes induced by hypertension. Moreover, ESAP-treatment was able to modulate the vascular and renal arterial reactivity and tissue redox state. The aforementioned data were accompanied by reduction of cardiac hypertrophy.CONCLUSION: In this study, we present important anatomical and phytochemical data that contributed to the correct identification and quality control of A. polystachya. In addition, we have shown that ESAP is safe after acute administration and present significant cardioprotective effects (at 30, 100, and 300\u00a0mg/kg doses) in SHRs after prolonged treatment.",
+    "Authors": "Marques AAM, Loren\u00e7one BR, Rom\u00e3o PVM, Guarnier LP, Palozi RAC, Moreno KGT, Tirloni CAS, Dos Santos AC, Souza RIC, Klider LM, Louren\u00e7o ELB, Tolouei SEL, Budel JM, Khan SI, Silva DB, Gasparotto Junior A",
+    "Authors short": "Marques AAM et al.",
+    "Citation": "Marques AAM et al. 2021",
+    "Created": 1666904581546,
+    "DOI": "10.1016/j.jep.2021.114077",
+    "Exclusion": false,
+    "Exclusion|Tier I": false,
+    "Exclusion|Tier II": false,
+    "Exclusion|Tier III": false,
+    "Full Citation": "Marques AAM, Loren\u00e7one BR, Rom\u00e3o PVM, Guarnier LP, Palozi RAC, Moreno KGT, Tirloni CAS, Dos Santos AC, Souza RIC, Klider LM, Louren\u00e7o ELB, Tolouei SEL, Budel JM, Khan SI, Silva DB, Gasparotto Junior A. Ethnopharmacological investigation of the cardiovascular effects of the ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke leaves in spontaneously hypertensive rats. J Ethnopharmacol 2021; 274 ():114077. ",
+    "Full text URL": "",
+    "HAWC ID": 11,
+    "HERO ID": null,
+    "Inclusion": true,
+    "Inclusion|Animal Study": false,
+    "Inclusion|Human Study": true,
+    "Inclusion|Mechanistic Study": false,
+    "Journal": "J Ethnopharmacol 2021; 274 ():114077",
+    "Last updated": 1666906611995,
+    "PubMed ID": 33789140,
+    "Title": "Ethnopharmacological investigation of the cardiovascular effects of the ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke leaves in spontaneously hypertensive rats.",
+    "User Tag Author": "Project Manager",
+    "User Tag Author ID": 2,
+    "User Tag ID": 4,
+    "User Tags Last Updated": 1666906611929,
+    "User Tags Resolved": true,
+    "Year": 2021
+  }
+]
\ No newline at end of file
diff --git a/tests/data/api/api-lit-assessment-references-export.json b/tests/data/api/api-lit-assessment-reference-export.json
similarity index 100%
rename from tests/data/api/api-lit-assessment-references-export.json
rename to tests/data/api/api-lit-assessment-reference-export.json
diff --git a/tests/data/fixtures/db.yaml b/tests/data/fixtures/db.yaml
index 72258eeb65..1814d8c5d5 100644
--- a/tests/data/fixtures/db.yaml
+++ b/tests/data/fixtures/db.yaml
@@ -158,6 +158,14 @@
   fields:
     app_label: lit
     model: reference
+- model: contenttypes.contenttype
+  fields:
+    app_label: lit
+    model: userreferencetags
+- model: contenttypes.contenttype
+  fields:
+    app_label: lit
+    model: userreferencetag
 - model: contenttypes.contenttype
   fields:
     app_label: riskofbias
@@ -780,6 +788,43 @@
     reviewers:
     - - reviewer@hawcproject.org
     epi_version: 1
+- model: assessment.assessment
+  pk: 4
+  fields:
+    name: Chemical A
+    year: 2022
+    version: draft
+    cas: ''
+    assessment_objective: <p>This assessment contains example references for testing
+      conflict resolution.</p>
+    authors: <p>The HAWCers</p>
+    creator:
+    - admin@hawcproject.org
+    editable: true
+    public_on: null
+    hide_from_public_page: false
+    enable_literature_review: true
+    enable_project_management: true
+    enable_data_extraction: true
+    enable_risk_of_bias: true
+    enable_bmd: true
+    enable_summary_text: true
+    conflicts_of_interest: ''
+    funding_source: ''
+    noel_name: 0
+    rob_name: 0
+    vocabulary: 1
+    modify_uncontrolled_vocabulary: true
+    epi_version: 2
+    created: 2022-10-27 21:02:28.805681+00:00
+    last_updated: 2022-10-27 21:02:28.805695+00:00
+    dtxsids: []
+    project_manager:
+    - - pm@hawcproject.org
+    team_members:
+    - - team@hawcproject.org
+    reviewers:
+    - - reviewer@hawcproject.org
 - model: assessment.attachment
   pk: 3
   fields:
@@ -1109,7 +1154,18 @@
   pk: 1
   fields:
     assessment: 1
+    conflict_resolution: false
     extraction_tag: 2
+    screening_instructions: ''
+    name_list_1: Positive
+    color_list_1: '#228833'
+    keyword_list_1: ''
+    name_list_2: Negative
+    color_list_2: '#EE6677'
+    keyword_list_2: ''
+    name_list_3: Additional
+    color_list_3: '#4477AA'
+    keyword_list_3: ''
     topic_tsne_data: ''
     topic_tsne_refresh_requested: null
     topic_tsne_last_refresh: null
@@ -1119,7 +1175,18 @@
   pk: 2
   fields:
     assessment: 2
+    conflict_resolution: false
     extraction_tag: 11
+    screening_instructions: ''
+    name_list_1: Positive
+    color_list_1: '#228833'
+    keyword_list_1: ''
+    name_list_2: Negative
+    color_list_2: '#EE6677'
+    keyword_list_2: ''
+    name_list_3: Additional
+    color_list_3: '#4477AA'
+    keyword_list_3: ''
     topic_tsne_data: ''
     topic_tsne_refresh_requested: null
     topic_tsne_last_refresh: null
@@ -1129,12 +1196,44 @@
   pk: 3
   fields:
     assessment: 3
+    conflict_resolution: false
     extraction_tag: 23
+    screening_instructions: ''
+    name_list_1: Positive
+    color_list_1: '#228833'
+    keyword_list_1: ''
+    name_list_2: Negative
+    color_list_2: '#EE6677'
+    keyword_list_2: ''
+    name_list_3: Additional
+    color_list_3: '#4477AA'
+    keyword_list_3: ''
     topic_tsne_data: ''
     topic_tsne_refresh_requested: null
     topic_tsne_last_refresh: null
     created: 2020-04-11 20:53:19.390979+00:00
     last_updated: 2020-04-11 20:53:19.390979+00:00
+- model: lit.literatureassessment
+  pk: 4
+  fields:
+    assessment: 4
+    conflict_resolution: true
+    extraction_tag: 32
+    screening_instructions: ''
+    name_list_1: Positive
+    color_list_1: '#228833'
+    keyword_list_1: 'burrito|Aloysia polystachya'
+    name_list_2: Negative
+    color_list_2: '#EE6677'
+    keyword_list_2: ''
+    name_list_3: Additional
+    color_list_3: '#4477AA'
+    keyword_list_3: ''
+    topic_tsne_data: ''
+    topic_tsne_refresh_requested: null
+    topic_tsne_last_refresh: null
+    created: 2022-10-27 21:02:29.769131+00:00
+    last_updated: 2022-10-27 21:02:29.769146+00:00
 - model: lit.search
   pk: 1
   fields:
@@ -1190,6 +1289,33 @@
     import_file: ''
     created: 2020-05-08 17:03:51.608549+00:00
     last_updated: 2020-05-08 17:03:51.608572+00:00
+- model: lit.search
+  pk: 6
+  fields:
+    assessment: 4
+    search_type: i
+    source: 0
+    title: Manual import
+    slug: manual-import
+    description: Default search instance used for manually importing literature into
+      HAWC instead of using a search.
+    search_string: None. This is used to manually enter literature.
+    import_file: ''
+    created: 2022-10-27 21:02:29.773641+00:00
+    last_updated: 2022-10-27 21:02:29.773652+00:00
+- model: lit.search
+  pk: 7
+  fields:
+    assessment: 4
+    search_type: i
+    source: 1
+    title: Burritos
+    slug: burritos
+    description: ''
+    search_string: 33789140, 28572920, 21296532
+    import_file: ''
+    created: 2022-10-27 21:03:01.264891+00:00
+    last_updated: 2022-10-27 21:03:01.264906+00:00
 - model: lit.identifiers
   pk: 2
   fields:
@@ -1239,6 +1365,141 @@
     database: 4
     content: ''
     url: ''
+- model: lit.identifiers
+  pk: 9
+  fields:
+    unique_id: '28572920'
+    database: 1
+    content: '{"xml": "<PubmedArticle></PubmedArticle>",
+      "PMID": 28572920, "title": "Nutrient content of fish powder from low value fish
+      and fish byproducts.", "abstract": "Consuming small-sized fish species whole,
+      and bones of large fish could contribute significantly to reducing the level
+      of micronutrient and protein malnutrition. These fish products are more affordable
+      and could therefore meet the needs of poor, vulnerable groups, particularly
+      in rural and urban areas where limited economic resources prevent dietary diversity.
+      The objectives of the study were to produce fish powder from dried edible byproducts
+      from fish processing factories, an underutilized fish species, burrito and to
+      determine the physical, micromineral, macronutrient and microbiological quality
+      of the dried fish powder. Edible fish processing byproducts and an underutilized
+      fish, burrito (Brachydeuterus auritus) were cleaned thoroughly and dried with
+      a Council for Scientific and Industrial Research-Food Research Institute (CSIR-FRI)
+      gas-fuelled oven at 55\u00b0C for 8\u00a0h or until dried. The dried products
+      were milled into powder, and packaged into polythene bags. Proximate analysis
+      of the fish powder was done Official Methods of Analysis (AOAC) methods. Minerals
+      and heavy metals in the fish powder were determined by atomic absorption spectrophotometry
+      (AAS). Microbiological quality was determined by Nordic Committee on Food Analysis
+      Method (NMLK) methods. Tuna trimmings contained 80.71\u00a0g/100\u00a0g protein,
+      whereas burrito contained 70.40\u00a0g/100\u00a0g protein. Concentrations of
+      cadmium, arsenic, and mercury varied from <1.00 to 1\u00a0mg/kg. Lead was found
+      at 0.04\u00a0mg/100\u00a0g in tuna frames and gills only. All fish byproducts
+      contained high levels of iron, for example, trimmings contained 16.58\u00a0mg/100\u00a0g,
+      whereas tuna frames and gills also contained 16.82 and 19.54\u00a0mg/100\u00a0g,
+      respectively. Burrito contained 8.92\u00a0mg/100\u00a0g. Zinc levels also ranged
+      from 0.41\u00a0mg/100\u00a0g in tuna trimmings to 1.88\u00a0mg/100\u00a0g in
+      tuna gills. The powdered samples according to the standards set by Ghana Standard
+      Authority, were acceptable. Consuming small- sized fish species whole, and bones
+      of large fish could contribute significantly to reducing the level of micronutrient
+      and protein malnutrition. These are more affordable and could therefore meet
+      the needs of poor, vulnerable groups.", "citation": "Food Sci Nutr 2017; 5 (3):374-379",
+      "year": 2017, "doi": "10.1002/fsn3.402", "authors": ["Abbey L", "Glover-Amengor
+      M", "Atikpo MO", "Atter A", "Toppe J"], "authors_short": "Abbey L et al."}'
+    url: ''
+- model: lit.identifiers
+  pk: 10
+  fields:
+    unique_id: '21296532'
+    database: 1
+    content: '{"xml": "<PubmedArticle></PubmedArticle>",
+      "PMID": 21296532, "title": "The \"Superhero Cape Burrito\": a simple and comfortable
+      method of short-term procedural restraint.", "abstract": "<span class=\"abstract_label\">BACKGROUND:
+      </span>Restraint is sometimes necessary to successfully perform procedures on
+      pediatric patients in the emergency department. A papoose may be intimidating
+      and uncomfortable, and a wrapped sheet may not keep the child''s arms from wiggling
+      free.<br><span class=\"abstract_label\">DISCUSSION: </span>We present an adaptation
+      of the wrapped sheet (burrito) technique, using a pillowcase to better immobilize
+      the child''s arms. The arms are inserted in the pillowcase behind the child''s
+      back, and then the child is placed supine over a horizontally placed sheet and
+      turned to each side so the sheet is tucked behind the back. The child is thereby
+      easily and comfortably restrained.<br><span class=\"abstract_label\">CONCLUSIONS:
+      </span>We believe this technique more successfully restrains the child than
+      the wrapped sheet alone, and is easier to execute than other wrapped sheet techniques
+      aimed at improving arm immobilization.", "citation": "J Emerg Med 2011; 41 (1):74-6",
+      "year": 2011, "doi": "10.1016/j.jemermed.2010.11.024", "authors": ["Brown JC",
+      "Klein EJ"], "authors_short": "Brown JC and Klein EJ"}'
+    url: ''
+- model: lit.identifiers
+  pk: 11
+  fields:
+    unique_id: '33789140'
+    database: 1
+    content: '{"xml": "<PubmedArticle></PubmedArticle>",
+      "PMID": 33789140, "title": "Ethnopharmacological investigation of the cardiovascular
+      effects of the ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke
+      leaves in spontaneously hypertensive rats.", "abstract": "<span class=\"abstract_label\">ETHNOPHARMACOLOGICAL
+      RELEVANCE: </span>Aloysia polystachya (Griseb) Moldenke (Verbenaceae), popularly
+      known as \"burrito\", is a South American species widely prescribed by local
+      Brazilian healers for the treatment of cardiovascular diseases. However, its
+      antihypertensive and cardioprotective effects are still unknown.<br><span class=\"abstract_label\">AIM:
+      </span>To evaluate the role of the ethanol-soluble fraction of A. polystachya
+      leaves (ESAP) against hypertension in spontaneously hypertensive rats (SHRs),
+      as well as its safety, morphoanatomical and phytochemical aspects.<br><span
+      class=\"abstract_label\">MATERIALS AND METHODS: </span>First, the leaves and
+      stems of A. polystachya were analyzed by optical and scanning electron microscopy
+      in order to provide anatomical data for quality control. Then, ESAP was obtained
+      and its chemical profile was analyzed by LC-DAD-MS. In addition, the cytotoxic
+      and acute toxicity potential of ESAP were evaluated in six cell lines and in
+      female Wistar rats, respectively. Next, female spontaneously hypertensive rats
+      (SHRs) received ESAP (30, 100, 300\u00a0mg/kg), hydrochlorothiazide (25\u00a0mg/kg),
+      or vehicle once daily for 28 days. Weekly kidney function was monitored by analyzing
+      urinary parameters. At the end of the 28-day treatment, the electrocardiographic
+      profile, blood pressure, and renal and mesenteric vascular reactivity were evaluated.
+      Relative organ (heart, kidney, and liver) weights and biochemical parameters
+      were also evaluated. Finally, the heart, kidneys, and aorta were collected for
+      determination of the tissue redox state, cardiac morphometry, and histopathological
+      analysis.<br><span class=\"abstract_label\">RESULTS: </span>The chemical profile
+      of ESAP was composed by organic acids, a nucleoside, methoxylated flavones and
+      glycosylated compounds including phenolic acids, phenylpropanoids, iridoids
+      and monoterpenes. No signs of toxicity were observed in all cell''s lines nor
+      in female Wistar rats submitted to this trial. All SHRs from the negative control
+      group presented a reduction in renal function, alterations in the renal and
+      mesenteric vascular reactivity, and electrocardiographic and morphometric changes
+      typical of ventricular hypertrophy. Oral prolonged ESAP-administration in SHRs
+      was able to reverse renal, electrocardiographic and hemodynamic changes induced
+      by hypertension. Moreover, ESAP-treatment was able to modulate the vascular
+      and renal arterial reactivity and tissue redox state. The aforementioned data
+      were accompanied by reduction of cardiac hypertrophy.<br><span class=\"abstract_label\">CONCLUSION:
+      </span>In this study, we present important anatomical and phytochemical data
+      that contributed to the correct identification and quality control of A. polystachya.
+      In addition, we have shown that ESAP is safe after acute administration and
+      present significant cardioprotective effects (at 30, 100, and 300\u00a0mg/kg
+      doses) in SHRs after prolonged treatment.", "citation": "J Ethnopharmacol 2021;
+      274 ():114077", "year": 2021, "doi": "10.1016/j.jep.2021.114077", "authors":
+      ["Marques AAM", "Loren\u00e7one BR", "Rom\u00e3o PVM", "Guarnier LP", "Palozi
+      RAC", "Moreno KGT", "Tirloni CAS", "Dos Santos AC", "Souza RIC", "Klider LM",
+      "Louren\u00e7o ELB", "Tolouei SEL", "Budel JM", "Khan SI", "Silva DB", "Gasparotto
+      Junior A"], "authors_short": "Marques AAM et al."}'
+    url: ''
+- model: lit.identifiers
+  pk: 12
+  fields:
+    unique_id: 10.1016/j.jemermed.2010.11.024
+    database: 4
+    content: ''
+    url: ''
+- model: lit.identifiers
+  pk: 13
+  fields:
+    unique_id: 10.1002/fsn3.402
+    database: 4
+    content: ''
+    url: ''
+- model: lit.identifiers
+  pk: 14
+  fields:
+    unique_id: 10.1016/j.jep.2021.114077
+    database: 4
+    content: ''
+    url: ''
 - model: lit.referencefiltertag
   pk: 1
   fields:
@@ -1479,6 +1740,78 @@
     numchild: 0
     name: Tier III
     slug: tier-iii
+- model: lit.referencefiltertag
+  pk: 31
+  fields:
+    path: '0004'
+    depth: 1
+    numchild: 2
+    name: assessment-4
+    slug: assessment-4
+- model: lit.referencefiltertag
+  pk: 32
+  fields:
+    path: '00040001'
+    depth: 2
+    numchild: 3
+    name: Inclusion
+    slug: inclusion
+- model: lit.referencefiltertag
+  pk: 33
+  fields:
+    path: '000400010001'
+    depth: 3
+    numchild: 0
+    name: Human Study
+    slug: human-study
+- model: lit.referencefiltertag
+  pk: 34
+  fields:
+    path: '000400010002'
+    depth: 3
+    numchild: 0
+    name: Animal Study
+    slug: animal-study
+- model: lit.referencefiltertag
+  pk: 35
+  fields:
+    path: '000400010003'
+    depth: 3
+    numchild: 0
+    name: Mechanistic Study
+    slug: mechanistic-study
+- model: lit.referencefiltertag
+  pk: 36
+  fields:
+    path: '00040002'
+    depth: 2
+    numchild: 3
+    name: Exclusion
+    slug: exclusion
+- model: lit.referencefiltertag
+  pk: 37
+  fields:
+    path: '000400020001'
+    depth: 3
+    numchild: 0
+    name: Tier I
+    slug: tier-i
+- model: lit.referencefiltertag
+  pk: 38
+  fields:
+    path: '000400020002'
+    depth: 3
+    numchild: 0
+    name: Tier II
+    slug: tier-ii
+- model: lit.referencefiltertag
+  pk: 39
+  fields:
+    path: '000400020003'
+    depth: 3
+    numchild: 0
+    name: Tier III
+    slug: tier-iii
 - model: lit.referencetags
   pk: 2
   fields:
@@ -1499,6 +1832,11 @@
   fields:
     tag: 13
     content_object: 6
+- model: lit.referencetags
+  pk: 6
+  fields:
+    tag: 33
+    content_object: 11
 - model: lit.reference
   pk: 1
   fields:
@@ -1710,6 +2048,198 @@
     - 5
     identifiers:
     - 3
+- model: lit.reference
+  pk: 9
+  fields:
+    assessment: 4
+    title: 'The "Superhero Cape Burrito": a simple and comfortable method of short-term
+      procedural restraint.'
+    authors_short: Brown JC and Klein EJ
+    authors: Brown JC, Klein EJ
+    year: 2011
+    journal: J Emerg Med 2011; 41 (1):74-6
+    abstract: '<span class="abstract_label">BACKGROUND: </span>Restraint is sometimes
+      necessary to successfully perform procedures on pediatric patients in the emergency
+      department. A papoose may be intimidating and uncomfortable, and a wrapped sheet
+      may not keep the child''s arms from wiggling free.<br><span class="abstract_label">DISCUSSION:
+      </span>We present an adaptation of the wrapped sheet (burrito) technique, using
+      a pillowcase to better immobilize the child''s arms. The arms are inserted in
+      the pillowcase behind the child''s back, and then the child is placed supine
+      over a horizontally placed sheet and turned to each side so the sheet is tucked
+      behind the back. The child is thereby easily and comfortably restrained.<br><span
+      class="abstract_label">CONCLUSIONS: </span>We believe this technique more successfully
+      restrains the child than the wrapped sheet alone, and is easier to execute than
+      other wrapped sheet techniques aimed at improving arm immobilization.'
+    full_text_url: ''
+    created: 2022-10-27 21:03:01.298079+00:00
+    last_updated: 2022-10-27 21:14:49.841918+00:00
+    block_id: null
+    searches:
+    - 7
+    identifiers:
+    - 10
+    - 12
+- model: lit.reference
+  pk: 10
+  fields:
+    assessment: 4
+    title: Nutrient content of fish powder from low value fish and fish byproducts.
+    authors_short: Abbey L et al.
+    authors: Abbey L, Glover-Amengor M, Atikpo MO, Atter A, Toppe J
+    year: 2017
+    journal: Food Sci Nutr 2017; 5 (3):374-379
+    abstract: Consuming small-sized fish species whole, and bones of large fish could
+      contribute significantly to reducing the level of micronutrient and protein
+      malnutrition. These fish products are more affordable and could therefore meet
+      the needs of poor, vulnerable groups, particularly in rural and urban areas
+      where limited economic resources prevent dietary diversity. The objectives of
+      the study were to produce fish powder from dried edible byproducts from fish
+      processing factories, an underutilized fish species, burrito and to determine
+      the physical, micromineral, macronutrient and microbiological quality of the
+      dried fish powder. Edible fish processing byproducts and an underutilized fish,
+      burrito (Brachydeuterus auritus) were cleaned thoroughly and dried with a Council
+      for Scientific and Industrial Research-Food Research Institute (CSIR-FRI) gas-fuelled
+      oven at 55°C for 8 h or until dried. The dried products were milled into powder,
+      and packaged into polythene bags. Proximate analysis of the fish powder was
+      done Official Methods of Analysis (AOAC) methods. Minerals and heavy metals
+      in the fish powder were determined by atomic absorption spectrophotometry (AAS).
+      Microbiological quality was determined by Nordic Committee on Food Analysis
+      Method (NMLK) methods. Tuna trimmings contained 80.71 g/100 g protein, whereas
+      burrito contained 70.40 g/100 g protein. Concentrations of cadmium, arsenic,
+      and mercury varied from <1.00 to 1 mg/kg. Lead was found at 0.04 mg/100 g in
+      tuna frames and gills only. All fish byproducts contained high levels of iron,
+      for example, trimmings contained 16.58 mg/100 g, whereas tuna frames and gills
+      also contained 16.82 and 19.54 mg/100 g, respectively. Burrito contained 8.92 mg/100 g.
+      Zinc levels also ranged from 0.41 mg/100 g in tuna trimmings to 1.88 mg/100 g
+      in tuna gills. The powdered samples according to the standards set by Ghana
+      Standard Authority, were acceptable. Consuming small- sized fish species whole,
+      and bones of large fish could contribute significantly to reducing the level
+      of micronutrient and protein malnutrition. These are more affordable and could
+      therefore meet the needs of poor, vulnerable groups.
+    full_text_url: ''
+    created: 2022-10-27 21:03:01.424371+00:00
+    last_updated: 2022-10-27 21:40:28.379536+00:00
+    block_id: null
+    searches:
+    - 7
+    identifiers:
+    - 9
+    - 13
+- model: lit.reference
+  pk: 11
+  fields:
+    assessment: 4
+    title: Ethnopharmacological investigation of the cardiovascular effects of the
+      ethanol-soluble fraction of Aloysia polystachya (Griseb.) Moldenke leaves in
+      spontaneously hypertensive rats.
+    authors_short: Marques AAM et al.
+    authors: Marques AAM, Lorençone BR, Romão PVM, Guarnier LP, Palozi RAC, Moreno
+      KGT, Tirloni CAS, Dos Santos AC, Souza RIC, Klider LM, Lourenço ELB, Tolouei
+      SEL, Budel JM, Khan SI, Silva DB, Gasparotto Junior A
+    year: 2021
+    journal: J Ethnopharmacol 2021; 274 ():114077
+    abstract: '<span class="abstract_label">ETHNOPHARMACOLOGICAL RELEVANCE: </span>Aloysia
+      polystachya (Griseb) Moldenke (Verbenaceae), popularly known as "burrito", is
+      a South American species widely prescribed by local Brazilian healers for the
+      treatment of cardiovascular diseases. However, its antihypertensive and cardioprotective
+      effects are still unknown.<br><span class="abstract_label">AIM: </span>To evaluate
+      the role of the ethanol-soluble fraction of A. polystachya leaves (ESAP) against
+      hypertension in spontaneously hypertensive rats (SHRs), as well as its safety,
+      morphoanatomical and phytochemical aspects.<br><span class="abstract_label">MATERIALS
+      AND METHODS: </span>First, the leaves and stems of A. polystachya were analyzed
+      by optical and scanning electron microscopy in order to provide anatomical data
+      for quality control. Then, ESAP was obtained and its chemical profile was analyzed
+      by LC-DAD-MS. In addition, the cytotoxic and acute toxicity potential of ESAP
+      were evaluated in six cell lines and in female Wistar rats, respectively. Next,
+      female spontaneously hypertensive rats (SHRs) received ESAP (30, 100, 300 mg/kg),
+      hydrochlorothiazide (25 mg/kg), or vehicle once daily for 28 days. Weekly kidney
+      function was monitored by analyzing urinary parameters. At the end of the 28-day
+      treatment, the electrocardiographic profile, blood pressure, and renal and mesenteric
+      vascular reactivity were evaluated. Relative organ (heart, kidney, and liver)
+      weights and biochemical parameters were also evaluated. Finally, the heart,
+      kidneys, and aorta were collected for determination of the tissue redox state,
+      cardiac morphometry, and histopathological analysis.<br><span class="abstract_label">RESULTS:
+      </span>The chemical profile of ESAP was composed by organic acids, a nucleoside,
+      methoxylated flavones and glycosylated compounds including phenolic acids, phenylpropanoids,
+      iridoids and monoterpenes. No signs of toxicity were observed in all cell''s
+      lines nor in female Wistar rats submitted to this trial. All SHRs from the negative
+      control group presented a reduction in renal function, alterations in the renal
+      and mesenteric vascular reactivity, and electrocardiographic and morphometric
+      changes typical of ventricular hypertrophy. Oral prolonged ESAP-administration
+      in SHRs was able to reverse renal, electrocardiographic and hemodynamic changes
+      induced by hypertension. Moreover, ESAP-treatment was able to modulate the vascular
+      and renal arterial reactivity and tissue redox state. The aforementioned data
+      were accompanied by reduction of cardiac hypertrophy.<br><span class="abstract_label">CONCLUSION:
+      </span>In this study, we present important anatomical and phytochemical data
+      that contributed to the correct identification and quality control of A. polystachya.
+      In addition, we have shown that ESAP is safe after acute administration and
+      present significant cardioprotective effects (at 30, 100, and 300 mg/kg doses)
+      in SHRs after prolonged treatment.'
+    full_text_url: ''
+    created: 2022-10-27 21:03:01.546964+00:00
+    last_updated: 2022-10-27 21:36:51.995510+00:00
+    block_id: null
+    searches:
+    - 7
+    identifiers:
+    - 11
+    - 14
+- model: lit.userreferencetags
+  pk: 1
+  fields:
+    tag: 33
+    content_object: 1
+- model: lit.userreferencetags
+  pk: 2
+  fields:
+    tag: 34
+    content_object: 2
+- model: lit.userreferencetags
+  pk: 5
+  fields:
+    tag: 33
+    content_object: 4
+- model: lit.userreferencetags
+  pk: 9
+  fields:
+    tag: 37
+    content_object: 5
+- model: lit.userreferencetag
+  pk: 1
+  fields:
+    user:
+    - team@hawcproject.org
+    reference: 11
+    is_resolved: true
+    created: 2022-10-27 21:07:50.089101+00:00
+    last_updated: 2022-10-27 21:07:50.420620+00:00
+- model: lit.userreferencetag
+  pk: 2
+  fields:
+    user:
+    - team@hawcproject.org
+    reference: 10
+    is_resolved: false
+    created: 2022-10-27 21:09:01.953588+00:00
+    last_updated: 2022-10-27 21:09:02.014507+00:00
+- model: lit.userreferencetag
+  pk: 4
+  fields:
+    user:
+    - pm@hawcproject.org
+    reference: 11
+    is_resolved: true
+    created: 2022-10-27 21:19:29.460709+00:00
+    last_updated: 2022-10-27 21:36:51.929199+00:00
+- model: lit.userreferencetag
+  pk: 5
+  fields:
+    user:
+    - pm@hawcproject.org
+    reference: 10
+    is_resolved: false
+    created: 2022-10-27 21:37:16.158160+00:00
+    last_updated: 2022-10-27 21:40:28.356071+00:00
 - model: study.study
   pk: 1
   fields:
@@ -2549,6 +3079,13 @@
     version: BMDS270
     created: 2020-11-25 16:47:36.244091+00:00
     last_updated: 2020-11-25 16:47:36.244109+00:00
+- model: bmd.assessmentsettings
+  pk: 4
+  fields:
+    assessment: 4
+    version: BMDS270
+    created: 2022-10-27 21:02:29.790125+00:00
+    last_updated: 2022-10-27 21:02:29.790139+00:00
 - model: bmd.logicfield
   pk: 1
   fields:
@@ -3362,6 +3899,277 @@
     continuous_on: true
     dichotomous_on: false
     cancer_dichotomous_on: false
+- model: bmd.logicfield
+  pk: 61
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.783955+00:00
+    last_updated: 2022-10-27 21:02:29.783985+00:00
+    name: BMD
+    description: A BMD value was successfully calculated.
+    failure_bin: 2
+    threshold: null
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 62
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784008+00:00
+    last_updated: 2022-10-27 21:02:29.784012+00:00
+    name: BMDL
+    description: A BMDL value was successfully calculated.
+    failure_bin: 2
+    threshold: null
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 63
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784040+00:00
+    last_updated: 2022-10-27 21:02:29.784043+00:00
+    name: BMDU
+    description: A BMDU value was successfully calculated.
+    failure_bin: 0
+    threshold: null
+    continuous_on: false
+    dichotomous_on: false
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 64
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784056+00:00
+    last_updated: 2022-10-27 21:02:29.784059+00:00
+    name: AIC
+    description: An AIC value was successfully calculated.
+    failure_bin: 2
+    threshold: null
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 65
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784071+00:00
+    last_updated: 2022-10-27 21:02:29.784074+00:00
+    name: Variance Type
+    description: 'The correct variance model was used (based on p-test #2).'
+    failure_bin: 1
+    threshold: '0.10'
+    continuous_on: true
+    dichotomous_on: false
+    cancer_dichotomous_on: false
+- model: bmd.logicfield
+  pk: 66
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784145+00:00
+    last_updated: 2022-10-27 21:02:29.784152+00:00
+    name: Variance Fit
+    description: 'The variance model appropriately captures the variance of the dataset
+      being modeled (based on p-test #3).'
+    failure_bin: 1
+    threshold: '0.10'
+    continuous_on: true
+    dichotomous_on: false
+    cancer_dichotomous_on: false
+- model: bmd.logicfield
+  pk: 67
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784169+00:00
+    last_updated: 2022-10-27 21:02:29.784173+00:00
+    name: GGOF
+    description: 'The mean model is appropriately modeling the dataset, based on the
+      global-goodness-of-fit (p-test #4).'
+    failure_bin: 1
+    threshold: '0.10'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: false
+- model: bmd.logicfield
+  pk: 68
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784186+00:00
+    last_updated: 2022-10-27 21:02:29.784189+00:00
+    name: GGOF (Cancer)
+    description: 'The mean model is appropriately modeling the dataset, based on the
+      global-goodness-of-fit (p-test #4).'
+    failure_bin: 1
+    threshold: '0.05'
+    continuous_on: false
+    dichotomous_on: false
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 69
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784203+00:00
+    last_updated: 2022-10-27 21:02:29.784206+00:00
+    name: BMD/BMDL (serious)
+    description: The ratio between the BMD and BMDL values is large, indicating a
+      large spread of estimates in the range of interest (warning).
+    failure_bin: 1
+    threshold: '20.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 70
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784221+00:00
+    last_updated: 2022-10-27 21:02:29.784224+00:00
+    name: BMD/BMDL (warning)
+    description: The ratio between the BMD and BMDL values is large, indicating a
+      large spread of estimates in the range of interest (caution).
+    failure_bin: 0
+    threshold: '5.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 71
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784237+00:00
+    last_updated: 2022-10-27 21:02:29.784240+00:00
+    name: Residual of Interest
+    description: The absolute value of the residual at the dose closest to the BMD
+      is large, which means the model may not be estimating well in this range.
+    failure_bin: 1
+    threshold: '2.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 72
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784252+00:00
+    last_updated: 2022-10-27 21:02:29.784255+00:00
+    name: Warnings
+    description: The model output file included additional warnings.
+    failure_bin: 0
+    threshold: null
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 73
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784288+00:00
+    last_updated: 2022-10-27 21:02:29.784292+00:00
+    name: BMD higher
+    description: The BMD estimate is higher than the maximum dose modeled, thus the
+      model is extrapolating a result.
+    failure_bin: 0
+    threshold: '1.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 74
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784326+00:00
+    last_updated: 2022-10-27 21:02:29.784330+00:00
+    name: BMDL higher
+    description: The BMDL estimate is higher than the maximum dose modeled, thus the
+      model is extrapolating a result.
+    failure_bin: 1
+    threshold: '1.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 75
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784342+00:00
+    last_updated: 2022-10-27 21:02:29.784346+00:00
+    name: Low BMD (warning)
+    description: The BMD estimate is lower than the lowest non-zero dose (caution).
+    failure_bin: 0
+    threshold: '3.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 76
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784359+00:00
+    last_updated: 2022-10-27 21:02:29.784362+00:00
+    name: Low BMDL (warning)
+    description: The BMDL estimate is lower than the lowest non-zero dose (caution).
+    failure_bin: 0
+    threshold: '3.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 77
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784390+00:00
+    last_updated: 2022-10-27 21:02:29.784393+00:00
+    name: Low BMD (serious)
+    description: The BMD estimate is lower than the lowest non-zero dose (warning).
+    failure_bin: 1
+    threshold: '10.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 78
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784420+00:00
+    last_updated: 2022-10-27 21:02:29.784423+00:00
+    name: Low BMDL (serious)
+    description: The BMDL estimate is lower than the lowest non-zero dose (warning).
+    failure_bin: 1
+    threshold: '10.00'
+    continuous_on: true
+    dichotomous_on: true
+    cancer_dichotomous_on: true
+- model: bmd.logicfield
+  pk: 79
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784435+00:00
+    last_updated: 2022-10-27 21:02:29.784439+00:00
+    name: Control residual
+    description: The absolute value of the residual at the control is large, which
+      is often used to estimate the BMR. Thus, the BMR estimate may be inaccurate.
+    failure_bin: 1
+    threshold: '2.00'
+    continuous_on: true
+    dichotomous_on: false
+    cancer_dichotomous_on: false
+- model: bmd.logicfield
+  pk: 80
+  fields:
+    assessment: 4
+    created: 2022-10-27 21:02:29.784451+00:00
+    last_updated: 2022-10-27 21:02:29.784454+00:00
+    name: Control stdev
+    description: The standard deviation estimate at the control is different the the
+      reported deviation, which is often used to estimate the BMR. Thus, the BMR estimate
+      may be inaccurate.
+    failure_bin: 1
+    threshold: '1.50'
+    continuous_on: true
+    dichotomous_on: false
+    cancer_dichotomous_on: false
 - model: bmd.session
   pk: 2
   fields:
@@ -4411,6 +5219,12 @@
       risk of bias metrics can be entered for a metric of bias for each study. Risk
       of Bias metrics are organized by domain. The following questions are required
       for evaluation for this assessment.</p>
+- model: riskofbias.riskofbiasassessment
+  pk: 5
+  fields:
+    assessment: 4
+    number_of_reviewers: 1
+    help_text: '<p>Help text.</p>'
 - model: epi.criteria
   pk: 1
   fields:
@@ -5450,6 +6264,13 @@
     depth: 1
     numchild: 0
     name: assessment-2
+- model: invitro.ivendpointcategory
+  pk: 2
+  fields:
+    path: '0002'
+    depth: 1
+    numchild: 0
+    name: assessment-4
 - model: invitro.ivendpoint
   pk: 5
   fields:
@@ -5678,6 +6499,18 @@
     text: <p>Level 1-B-beta text.</p>
     created: 2020-11-25 20:25:37.723914+00:00
     last_updated: 2020-11-25 20:25:37.723938+00:00
+- model: summary.summarytext
+  pk: 10
+  fields:
+    path: '0004'
+    depth: 1
+    numchild: 0
+    assessment: 4
+    title: assessment-4
+    slug: assessment-4-slug
+    text: Root-level text
+    created: 2022-10-27 21:02:29.800145+00:00
+    last_updated: 2022-10-27 21:02:29.800198+00:00
 - model: summary.summarytable
   pk: 1
   fields:
diff --git a/tests/hawc/apps/assessment/test_views.py b/tests/hawc/apps/assessment/test_views.py
index b06af359b0..4c6e4668e2 100644
--- a/tests/hawc/apps/assessment/test_views.py
+++ b/tests/hawc/apps/assessment/test_views.py
@@ -64,7 +64,7 @@ def test_counts(self):
         url = reverse("about")
         response = client.get(url)
         assert "counts" in response.context
-        assert response.context["counts"]["assessments"] == 3
+        assert response.context["counts"]["assessments"] == 4
         assert response.context["counts"]["users"] == 5
 
 
diff --git a/tests/hawc/apps/lit/test_api.py b/tests/hawc/apps/lit/test_api.py
index 45c1460837..e71f864ab7 100644
--- a/tests/hawc/apps/lit/test_api.py
+++ b/tests/hawc/apps/lit/test_api.py
@@ -1,6 +1,7 @@
 import json
 from io import BytesIO
 from pathlib import Path
+from typing import Optional
 
 import pandas as pd
 import pytest
@@ -10,6 +11,7 @@
 
 from hawc.apps.common.forms import ASSESSMENT_UNIQUE_MESSAGE
 from hawc.apps.lit import constants, models
+from hawc.apps.myuser.models import HAWCUser
 
 from ..test_utils import check_details_of_last_log_entry
 
@@ -18,10 +20,13 @@
 
 @pytest.mark.django_db
 class TestLiteratureAssessmentViewset:
-    def _test_flat_export(self, rewrite_data_files: bool, fn: str, url: str):
+    def _test_flat_export(
+        self, rewrite_data_files: bool, fn: str, url: str, client: Optional[APIClient] = None
+    ):
+        if client is None:
+            client = APIClient()
+            assert client.login(username="reviewer@hawcproject.org", password="pw") is True
 
-        client = APIClient()
-        assert client.login(username="reviewer@hawcproject.org", password="pw") is True
         resp = client.get(url)
         assert resp.status_code == 200
 
@@ -47,33 +52,60 @@ def test_permissions(self, db_keys):
             reverse(
                 "lit:api:assessment-reference-year-histogram", args=(db_keys.assessment_working,)
             ),
-            reverse("lit:api:assessment-references-download", args=(db_keys.assessment_working,)),
+            reverse("lit:api:assessment-reference-export", args=(db_keys.assessment_working,)),
             reverse("lit:api:assessment-tag-heatmap", args=(db_keys.assessment_working,)),
+            reverse("lit:api:assessment-topic-model", args=(db_keys.assessment_working,)),
+            reverse("lit:api:assessment-tagtree", args=(db_keys.assessment_working,)),
         ]
         for url in urls:
             assert anon_client.get(url).status_code == 403
             assert rev_client.get(url).status_code == 200
 
-        # topic model permissions
-        url = reverse("lit:api:assessment-topic-model", args=(db_keys.assessment_working,))
-        assert anon_client.get(url).status_code == 403
-        assert rev_client.get(url).status_code == 200
-
-        tagtree_url = reverse("lit:api:assessment-tagtree", args=(db_keys.assessment_working,))
-        # only reviewers and up can GET
-        assert anon_client.get(tagtree_url).status_code == 403
-        assert rev_client.get(tagtree_url).status_code == 200
+        # user-tag data
+        for url in [
+            reverse("lit:api:assessment-user-tag-export", args=(db_keys.assessment_final,)),
+        ]:
+            assert anon_client.get(url).status_code == 403
+            assert rev_client.get(url).status_code == 403
+            assert pm_client.get(url).status_code == 200
 
         # only admins and up can POST
-        assert anon_client.post(tagtree_url).status_code == 403
-        assert rev_client.post(tagtree_url).status_code == 403
-        assert pm_client.post(tagtree_url, None).status_code != 403
-
-    def test_references_download(self, rewrite_data_files: bool, db_keys):
-        url = reverse("lit:api:assessment-references-download", args=(db_keys.assessment_final,))
-        fn = "api-lit-assessment-references-export.json"
+        url = reverse("lit:api:assessment-tagtree", args=(db_keys.assessment_working,))
+        assert anon_client.post(url).status_code == 403
+        assert rev_client.post(url).status_code == 403
+        assert pm_client.post(url, None).status_code == 400  # validation; not permission error
+
+    def test_export(self, rewrite_data_files: bool, db_keys):
+        url = reverse("lit:api:assessment-reference-export", args=(db_keys.assessment_final,))
+        fn = "api-lit-assessment-reference-export.json"
         self._test_flat_export(rewrite_data_files, fn, url)
 
+    def test_references_export_format(self, db_keys):
+        url = reverse("lit:api:assessment-reference-export", args=(db_keys.assessment_final,))
+        c = APIClient()
+        assert c.login(email="team@hawcproject.org", password="pw") is True
+
+        # base report; reference metadata plus columns for each tag
+        resp = c.get(url, {"tag": 12}).json()
+        assert len(resp) == 2
+        assert len(resp[0]) > 20
+        assert resp[0]["Inclusion"] is True
+
+        # table builder format
+        resp = c.get(url, {"tag": 12, "export_format": "table-builder"}).json()
+        assert len(resp) == 2
+        assert len(resp[0]) == 5
+        assert resp[0]["Name"] == "Kawana N, Ishimatsu S, and Kanda K 2001"
+
+    def test_export_user_tags(self, rewrite_data_files: bool, db_keys):
+        pm_client = APIClient()
+        assert pm_client.login(username="pm@hawcproject.org", password="pw") is True
+        url = reverse(
+            "lit:api:assessment-user-tag-export", args=(db_keys.assessment_conflict_resolution,)
+        )
+        fn = "api-lit-assessment-reference-export-user-tags.json"
+        self._test_flat_export(rewrite_data_files, fn, url, pm_client)
+
     def test_tags(self, db_keys):
         url = reverse("lit:api:assessment-tags", kwargs=dict(pk=db_keys.assessment_working))
         c = APIClient()
@@ -261,31 +293,6 @@ def test_excel_to_json(self, db_keys):
         assert resp.status_code == 400 and resp.json() == {"file": "Unable to parse excel file"}
 
 
-@pytest.mark.django_db
-class TestReferenceFilterTagViewset:
-    def test_references(self):
-        url = reverse("lit:api:tags-references", args=(12,))
-        c = APIClient()
-        assert c.login(email="pm@hawcproject.org", password="pw") is True
-
-        # base report; reference metadata plus columns for each tag
-        resp = c.get(url).json()
-        assert len(resp) == 2
-        assert len(resp[0]) > 20
-        assert resp[0]["Inclusion"] is True
-
-        # table builder format
-        resp = c.get(url, {"exporter": "table-builder"}).json()
-        assert len(resp) == 2
-        assert len(resp[0]) == 5
-        assert resp[0]["Name"] == "Kawana N, Ishimatsu S, and Kanda K 2001"
-
-        # invalid exporter format
-        resp = c.get(url, {"exporter": "not-a-format"})
-        assert resp.status_code == 400
-        assert resp.json() == {"exporter": ['"not-a-format" is not a valid choice.']}
-
-
 @pytest.mark.vcr
 @pytest.mark.django_db
 class TestSearchViewset:
@@ -580,8 +587,8 @@ def test_valid_requests(self, db_keys):
 
 
 @pytest.mark.django_db
-class TestReferenceUpdateApi:
-    def test_permissions(self, db_keys):
+class TestReferenceViewset:
+    def test_update_permissions(self, db_keys):
 
         url = reverse("lit:api:reference-detail", args=(db_keys.reference_linked,))
         data = {"title": "TestReferenceUpdateApi test"}
@@ -605,7 +612,7 @@ def test_permissions(self, db_keys):
         # make sure the object hasn't changed
         assert post_ref == pre_ref
 
-    def test_bad_requests(self, db_keys):
+    def test_update_bad_requests(self, db_keys):
         # test bad id
         url = reverse("lit:api:reference-detail", args=(-1,))
         data = {"title": "TestReferenceUpdateApi test"}
@@ -623,7 +630,7 @@ def test_bad_requests(self, db_keys):
         assert response.status_code == 400
         assert response.json() == {"tags": ["All tag ids are not from this assessment"]}
 
-    def test_valid_requests(self, db_keys):
+    def test_update_valid_requests(self, db_keys):
         url = reverse("lit:api:reference-detail", args=(db_keys.reference_linked,))
         client = APIClient()
         assert client.login(username="team@hawcproject.org", password="pw") is True
@@ -661,3 +668,128 @@ def test_valid_requests(self, db_keys):
         assert updated_reference.tags.count() == len(tags)
         for id in tags:
             assert updated_reference.tags.filter(id=id).exists()
+
+    def test_tag_permissions(self, db_keys):
+        ref: models.Reference = models.Reference.objects.get(id=db_keys.reference_tag_conflict)
+        update_tags_url = reverse("lit:api:reference-tag", args=(ref.pk,))
+        resolve_conflict_url = reverse("lit:api:reference-resolve-conflict", args=(ref.pk,))
+
+        assert ref.tags.count() == 0
+        assert ref.user_tags.count() == 2
+        assert ref.user_tags.filter(user__email="reviewer@hawcproject.org").exists() is False
+        assert ref.has_user_tag_conflicts() is True
+
+        # reviewers can't update tags or resolve conflicts
+        client = APIClient()
+        assert client.login(username="reviewer@hawcproject.org", password="pw") is True
+        response = client.post(update_tags_url, data={"tags": [32, 33]}, format="json")
+        assert response.status_code == 403
+
+        # no user tag was created
+        ref.refresh_from_db()
+        assert ref.user_tags.count() == 2
+        assert ref.user_tags.filter(user__email="reviewer@hawcproject.org").exists() is False
+
+        user_tag_id = ref.user_tags.first().pk
+        response = client.post(resolve_conflict_url, data={"user_tag_id": user_tag_id})
+        assert response.status_code == 403
+
+        # tags were not added to the reference/conflict was not resolved
+        ref.refresh_from_db()
+        assert ref.tags.count() == 0
+        assert ref.has_user_tag_conflicts() is True
+
+    def test_conflict_valid(self, db_keys):
+        ref: models.Reference = models.Reference.objects.get(id=db_keys.reference_untagged)
+        pm = HAWCUser.objects.get(email="pm@hawcproject.org")
+        tm = HAWCUser.objects.get(email="team@hawcproject.org")
+
+        pm_tags = [32]
+        tm_tags = [33]
+
+        update_tags_url = reverse("lit:api:reference-tag", args=(ref.pk,))
+        resolve_conflict_url = reverse("lit:api:reference-resolve-conflict", args=(ref.pk,))
+
+        client = APIClient()
+
+        assert ref.user_tags.count() == 0
+        assert ref.has_user_tag_conflicts() is False
+
+        # create a conflict by applying different tags as two different users
+        assert client.login(email=tm.email, password="pw") is True
+        response = client.post(update_tags_url, data={"tags": tm_tags}, format="json")
+        assert response.status_code == 200
+        client.logout()
+
+        assert client.login(email=pm.email, password="pw") is True
+        response = client.post(update_tags_url, data={"tags": pm_tags}, format="json")
+        assert response.status_code == 200
+
+        ref.refresh_from_db()
+        assert ref.user_tags.count() == 2
+        assert ref.has_user_tag_conflicts() is True
+        assert ref.tags.count() == 0
+
+        # resolve the conflict using Project Manager's tags
+        pm_user_tag = ref.user_tags.get(user=pm)
+        response = client.post(resolve_conflict_url, data={"user_tag_id": pm_user_tag.pk})
+        assert response.status_code == 200
+
+        ref.refresh_from_db()
+        assert ref.has_user_tag_conflicts() is False
+        assert list(ref.tags.values_list("id", flat=True)) == pm_tags
+
+    def test_tagging_valid(self, db_keys):
+        ref: models.Reference = models.Reference.objects.get(id=db_keys.reference_untagged)
+        client = APIClient()
+        assert client.login(email="pm@hawcproject.org", password="pw") is True
+
+        # test applying tags w/o conflict resolution
+        ref = models.Reference.objects.get(id=db_keys.reference_linked)
+        update_tags_url = reverse("lit:api:reference-tag", args=(ref.pk,))
+        tags = [2, 3]
+        response = client.post(update_tags_url, data={"tags": tags}, format="json")
+        assert response.status_code == 200
+
+        # tags are applied to the reference without creating a conflict
+        ref.refresh_from_db()
+        assert list(ref.tags.values_list("id", flat=True)) == tags
+        assert ref.has_user_tag_conflicts() is False
+
+    def test_tagging_invalid(self, db_keys):
+        client = APIClient()
+        assert client.login(username="team@hawcproject.org", password="pw") is True
+        url = reverse("lit:api:reference-tag", args=(-1,))
+
+        # test bad id
+        data = {"tags": [2, 3]}
+        response = client.post(url, data, format="json")
+        assert response.status_code == 404
+
+        # test bad tag
+        url = reverse("lit:api:reference-tag", args=(db_keys.reference_linked,))
+        data = {"tags": [2, 3, -1]}
+        response = client.post(url, data, format="json")
+        assert response.status_code == 400
+        assert response.json() == {"tags": "Array of tags must be valid primary keys"}
+
+    def test_conflict_invalid(self, db_keys):
+        client = APIClient()
+        assert client.login(username="team@hawcproject.org", password="pw") is True
+        url = reverse("lit:api:reference-resolve-conflict", args=(-1,))
+
+        # test bad ref id
+        data = {"user_tag_id": 1}
+        response = client.post(url, data, format="json")
+        assert response.status_code == 404
+
+        # test bad user_tag_id (wrong reference)
+        url = reverse("lit:api:reference-resolve-conflict", args=(db_keys.reference_tag_conflict,))
+        data = {"user_tag_id": 1}
+        response = client.post(url, data, format="json")
+        assert response.status_code == 404
+
+        # test bad user_tag_id (doesn't exist)
+        data = {"user_tag_id": -1}
+        response = client.post(url, data, format="json")
+        assert response.status_code == 404
diff --git a/tests/hawc/apps/lit/test_forms.py b/tests/hawc/apps/lit/test_forms.py
index af1c33a1b8..f16259e7f5 100644
--- a/tests/hawc/apps/lit/test_forms.py
+++ b/tests/hawc/apps/lit/test_forms.py
@@ -34,21 +34,21 @@ def test_extraction_tag_queryset(self, db_keys):
         assert len(form2.fields["extraction_tag"].choices) == 9
 
         # ensure we can save `extraction_tag == None`
-        form = LiteratureAssessmentForm(data={"extraction_tag": None}, instance=instance1)
+        data = form1.initial.copy()
+        data.update(extraction_tag=None)
+        form = LiteratureAssessmentForm(data=data, instance=instance1)
         assert form.is_valid()
 
         # ensure that a choice from our assessment is valid
-        form = LiteratureAssessmentForm(
-            data={"extraction_tag": form1.fields["extraction_tag"].choices[-1][0]},
-            instance=instance1,
-        )
+        data = form1.initial.copy()
+        data.update(extraction_tag=form1.fields["extraction_tag"].choices[-1][0])
+        form = LiteratureAssessmentForm(data=data, instance=instance1)
         assert form.is_valid()
 
         # ensure that a choice from another assessment is invalid
-        form = LiteratureAssessmentForm(
-            data={"extraction_tag": form2.fields["extraction_tag"].choices[-1][0]},
-            instance=instance1,
-        )
+        data = form1.initial.copy()
+        data.update(extraction_tag=form2.fields["extraction_tag"].choices[-1][0])
+        form = LiteratureAssessmentForm(data=data, instance=instance1)
         assert form.is_valid() is False
         assert (
             form.errors["extraction_tag"][0]
diff --git a/tests/hawc/apps/lit/test_models.py b/tests/hawc/apps/lit/test_models.py
index 4d82cbc191..ab50362567 100644
--- a/tests/hawc/apps/lit/test_models.py
+++ b/tests/hawc/apps/lit/test_models.py
@@ -1,6 +1,7 @@
 import pytest
 from django.core.exceptions import ObjectDoesNotExist
 
+from hawc.apps.assessment.models import HAWCUser
 from hawc.apps.lit.models import Reference, ReferenceFilterTag, Search
 from hawc.apps.study.models import Study
 
@@ -62,6 +63,32 @@ def test_to_dict(self):
         data = ref.to_json()
         assert isinstance(data, str)
 
+    def test_update_tags(self):
+        # Test that conflict resolution is working
+        ref = Reference.objects.get(id=9)
+        pm = HAWCUser.objects.get(email="pm@hawcproject.org")
+        tm = HAWCUser.objects.get(email="team@hawcproject.org")
+
+        # User #1 adds user tags, but not applied to reference since we need 2+ people
+        tags = [32]
+        ref.update_tags(pm, tags)
+        ref.refresh_from_db()
+        assert ref.has_user_tag_conflicts() is True
+        assert ref.tags.count() == 0
+
+        # User #2 adds the same tags, those are now applied to the reference tag
+        ref.update_tags(tm, tags)
+        ref.refresh_from_db()
+        assert ref.has_user_tag_conflicts() is False
+        assert ref.tags.count() > 0
+
+        # User #2 changes their tags, now there are conflicts again; the reference is unchanged
+        new_tags = [33]
+        ref.update_tags(tm, new_tags)
+        ref.refresh_from_db()
+        assert ref.has_user_tag_conflicts() is True
+        assert list(ref.tags.values_list("id", flat=True)) == tags
+
 
 @pytest.mark.vcr
 @pytest.mark.django_db
diff --git a/tests/hawc/apps/lit/test_views.py b/tests/hawc/apps/lit/test_views.py
index f2f89acfae..72c08d147e 100644
--- a/tests/hawc/apps/lit/test_views.py
+++ b/tests/hawc/apps/lit/test_views.py
@@ -4,6 +4,33 @@
 from pytest_django.asserts import assertTemplateNotUsed, assertTemplateUsed
 
 
+@pytest.mark.django_db
+class TestViewPermissions:
+    def test_success(self, db_keys):
+        clients = ["admin@hawcproject.org", "pm@hawcproject.org", "team@hawcproject.org"]
+        views = [
+            reverse("lit:tag-status", args=(3,)),
+            reverse("lit:tag-conflicts", args=(db_keys.assessment_working,)),
+        ]
+        for client in clients:
+            c = Client()
+            assert c.login(username=client, password="pw") is True
+            for view in views:
+                response = c.get(view)
+                assert response.status_code == 200
+
+    def test_failure(self, db_keys):
+        # anonymous user
+        c = Client()
+        views = [
+            (reverse("lit:tag-status", args=(3,)), 403),
+            (reverse("lit:tag-conflicts", args=(db_keys.assessment_working,)), 403),
+        ]
+        for (url, status) in views:
+            response = c.get(url)
+            assert response.status_code == status
+
+
 @pytest.mark.django_db
 def test_reference_delete(db_keys):
     c = Client()
diff --git a/tests/integration/test_lit.py b/tests/integration/test_lit.py
index 924fcb2a5b..4d8cc0f05f 100644
--- a/tests/integration/test_lit.py
+++ b/tests/integration/test_lit.py
@@ -1,3 +1,5 @@
+import re
+
 from playwright.sync_api import expect
 
 from .common import PlaywrightTestCase
@@ -15,11 +17,11 @@ def test_literature(self):
         assert page.locator("css=#tags .nestedTag").count() > 5
 
         # /lit/assessment/:id/references/
-        page.locator("text=View By Tag").click()
+        page.locator("text=Browse").click()
         expect(page).to_have_url(self.live_server_url + "/lit/assessment/2/references/")
-        expect(page.locator("text=Human Study (2)")).to_be_visible()
+        expect(page.locator("text=Human Study")).to_be_visible()
         expect(page.locator("#references_detail_div")).not_to_have_count(0)
-        page.locator("text=Human Study (2)").click()
+        page.locator("text=Human Study").click()
         expect(page.locator(".referenceDetail")).to_have_count(2)
 
         # /lit/assessment/:id/references/visualization/
@@ -34,9 +36,9 @@ def test_literature(self):
         page.locator("text=Apply filters").click()
         expect(page.locator("text=References (1 found)")).to_be_visible()
 
-        # /lit/assessment/:id/tag/untagged/
-        page.goto(self.live_server_url + "/lit/assessment/1/tag/untagged/")
-        expect(page.locator("text=Tags for current reference")).to_be_visible()
+        # /lit/assessment/:id/tag/
+        page.goto(self.live_server_url + "/lit/assessment/1/tag/")
+        expect(page.locator("text=Currently Applied Tags")).to_be_visible()
 
         # /lit/assessment/:id/tags/update/
         page.goto(self.live_server_url + "/lit/assessment/1/tags/update/")
@@ -47,3 +49,39 @@ def test_literature(self):
         expect(
             page.locator("text=Select an Excel file (.xlsx) to load and process.")
         ).to_be_visible()
+
+    def test_conflict_resolution(self):
+        page = self.page
+        page.goto(self.live_server_url)
+
+        # /lit/assessment/:id/
+        self.login_and_goto_url(
+            page,
+            f"{self.live_server_url}/lit/assessment/4/reference-tag-conflicts/",
+            "pm@hawcproject.org",
+        )
+
+        expect(
+            page.locator(
+                "text=Nutrient content of fish powder from low value fish and fish byproducts."
+            )
+        ).to_be_visible()
+        expect(page.locator(".user-tag")).to_have_count(2)
+        expect(page.locator(".conflict-reference-li")).to_have_count(1)
+
+        with page.expect_response(re.compile(r"resolve_conflict")) as response_info:
+            page.locator("text=Approve Team Member >> button").click()
+            response = response_info.value
+            assert response.ok
+
+        # hides reference now that conflict is resolved
+        expect(page.locator(".conflict-reference-li")).not_to_be_visible()
+
+        # check that selected tag has been applied
+        page.goto(f"{self.live_server_url}/lit/assessment/4/references/")
+        page.locator("text=Animal Study").click()
+        expect(
+            page.locator(
+                "text=Nutrient content of fish powder from low value fish and fish byproducts."
+            )
+        ).to_be_visible()