about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.env.nanobox30
-rw-r--r--.env.production.sample3
-rw-r--r--CODE_OF_CONDUCT.md46
-rw-r--r--Dockerfile2
-rw-r--r--Gemfile32
-rw-r--r--Gemfile.lock197
-rw-r--r--app/controllers/admin/account_moderation_notes_controller.rb56
-rw-r--r--app/controllers/admin/accounts_controller.rb31
-rw-r--r--app/controllers/admin/base_controller.rb4
-rw-r--r--app/controllers/admin/confirmations_controller.rb9
-rw-r--r--app/controllers/admin/custom_emojis_controller.rb16
-rw-r--r--app/controllers/admin/domain_blocks_controller.rb9
-rw-r--r--app/controllers/admin/email_domain_blocks_controller.rb5
-rw-r--r--app/controllers/admin/instances_controller.rb2
-rw-r--r--app/controllers/admin/reported_statuses_controller.rb9
-rw-r--r--app/controllers/admin/reports_controller.rb3
-rw-r--r--app/controllers/admin/resets_controller.rb9
-rw-r--r--app/controllers/admin/roles_controller.rb25
-rw-r--r--app/controllers/admin/settings_controller.rb3
-rw-r--r--app/controllers/admin/silences_controller.rb2
-rw-r--r--app/controllers/admin/statuses_controller.rb17
-rw-r--r--app/controllers/admin/subscriptions_controller.rb1
-rw-r--r--app/controllers/admin/suspensions_controller.rb4
-rw-r--r--app/controllers/admin/two_factor_authentications_controller.rb1
-rw-r--r--app/controllers/api/v1/lists/accounts_controller.rb81
-rw-r--r--app/controllers/api/v1/lists_controller.rb79
-rw-r--r--app/controllers/api/v1/reports_controller.rb2
-rw-r--r--app/controllers/api/v1/search_controller.rb16
-rw-r--r--app/controllers/api/v1/timelines/home_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/list_controller.rb66
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/auth/sessions_controller.rb2
-rw-r--r--app/controllers/concerns/authorization.rb1
-rw-r--r--app/controllers/home_controller.rb1
-rw-r--r--app/controllers/settings/notifications_controller.rb2
-rw-r--r--app/helpers/admin/filter_helper.rb2
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/javascript/glitch/components/account/header.js4
-rw-r--r--app/javascript/glitch/components/status/action_bar.js5
-rw-r--r--app/javascript/glitch/components/status/container.js2
-rw-r--r--app/javascript/glitch/components/status/index.js12
-rw-r--r--app/javascript/mastodon/actions/pin_statuses.js5
-rw-r--r--app/javascript/mastodon/actions/streaming.js57
-rw-r--r--app/javascript/mastodon/components/account.js6
-rw-r--r--app/javascript/mastodon/components/icon_button.js24
-rw-r--r--app/javascript/mastodon/components/media_gallery.js13
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js2
-rw-r--r--app/javascript/mastodon/components/status.js8
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js5
-rw-r--r--app/javascript/mastodon/containers/account_container.js5
-rw-r--r--app/javascript/mastodon/containers/compose_container.js5
-rw-r--r--app/javascript/mastodon/containers/mastodon.js9
-rw-r--r--app/javascript/mastodon/containers/status_container.js22
-rw-r--r--app/javascript/mastodon/containers/timeline_container.js5
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js4
-rw-r--r--app/javascript/mastodon/features/account/components/header.js20
-rw-r--r--app/javascript/mastodon/features/account_gallery/index.js7
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js5
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js5
-rw-r--r--app/javascript/mastodon/features/account_timeline/index.js5
-rw-r--r--app/javascript/mastodon/features/blocks/index.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js8
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js9
-rw-r--r--app/javascript/mastodon/features/compose/containers/compose_form_container.js1
-rw-r--r--app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js1
-rw-r--r--app/javascript/mastodon/features/compose/containers/navigation_container.js3
-rw-r--r--app/javascript/mastodon/features/compose/containers/warning_container.js3
-rw-r--r--app/javascript/mastodon/features/compose/util/counter.js2
-rw-r--r--app/javascript/mastodon/features/emoji/__tests__/emoji-test.js16
-rw-r--r--app/javascript/mastodon/features/emoji/emoji.js38
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_compressed.js4
-rw-r--r--app/javascript/mastodon/features/favourites/index.js2
-rw-r--r--app/javascript/mastodon/features/follow_requests/index.js2
-rw-r--r--app/javascript/mastodon/features/followers/index.js2
-rw-r--r--app/javascript/mastodon/features/following/index.js2
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js9
-rw-r--r--app/javascript/mastodon/features/mutes/index.js2
-rw-r--r--app/javascript/mastodon/features/reblogs/index.js2
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js4
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js2
-rw-r--r--app/javascript/mastodon/features/status/index.js41
-rw-r--r--app/javascript/mastodon/features/ui/components/mute_modal.js8
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js25
-rw-r--r--app/javascript/mastodon/features/ui/containers/status_list_container.js4
-rw-r--r--app/javascript/mastodon/features/ui/index.js30
-rw-r--r--app/javascript/mastodon/features/ui/util/optional_motion.js57
-rw-r--r--app/javascript/mastodon/features/ui/util/react_router_helpers.js18
-rw-r--r--app/javascript/mastodon/features/ui/util/reduced_motion.js44
-rw-r--r--app/javascript/mastodon/initial_state.js21
-rw-r--r--app/javascript/mastodon/locales/ko.json2
-rw-r--r--app/javascript/mastodon/locales/oc.json22
-rw-r--r--app/javascript/mastodon/locales/pl.json4
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json2
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json224
-rw-r--r--app/javascript/mastodon/reducers/compose.js3
-rw-r--r--app/javascript/mastodon/reducers/custom_emojis.js2
-rw-r--r--app/javascript/mastodon/reducers/meta.js1
-rw-r--r--app/javascript/mastodon/reducers/mutes.js2
-rw-r--r--app/javascript/mastodon/stream.js61
-rw-r--r--app/javascript/packs/custom.js1
-rw-r--r--app/javascript/styles/mastodon/accounts.scss18
-rw-r--r--app/javascript/styles/mastodon/components.scss40
-rw-r--r--app/javascript/styles/mastodon/landing_strip.scss7
-rw-r--r--app/javascript/themes/default/theme.yml17
-rw-r--r--app/javascript/themes/spin/pack.js2
-rw-r--r--app/javascript/themes/spin/style.scss14
-rw-r--r--app/javascript/themes/spin/theme.yml2
-rw-r--r--app/lib/activitypub/activity/create.rb2
-rw-r--r--app/lib/extractor.rb3
-rw-r--r--app/lib/feed_manager.rb73
-rw-r--r--app/lib/formatter.rb26
-rw-r--r--app/lib/language_detector.rb31
-rw-r--r--app/lib/themes.rb9
-rw-r--r--app/mailers/notification_mailer.rb18
-rw-r--r--app/mailers/user_mailer.rb6
-rw-r--r--app/models/account.rb24
-rw-r--r--app/models/account_domain_block.rb2
-rw-r--r--app/models/block.rb2
-rw-r--r--app/models/concerns/account_finder_concern.rb2
-rw-r--r--app/models/concerns/account_interactions.rb5
-rw-r--r--app/models/conversation_mute.rb2
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/domain_block.rb2
-rw-r--r--app/models/email_domain_block.rb23
-rw-r--r--app/models/favourite.rb2
-rw-r--r--app/models/feed.rb23
-rw-r--r--app/models/follow.rb2
-rw-r--r--app/models/follow_request.rb6
-rw-r--r--app/models/home_feed.rb25
-rw-r--r--app/models/import.rb2
-rw-r--r--app/models/list.rb22
-rw-r--r--app/models/list_account.rb24
-rw-r--r--app/models/list_feed.rb8
-rw-r--r--app/models/media_attachment.rb2
-rw-r--r--app/models/mention.rb2
-rw-r--r--app/models/mute.rb4
-rw-r--r--app/models/notification.rb2
-rw-r--r--app/models/report.rb2
-rw-r--r--app/models/session_activation.rb8
-rw-r--r--app/models/setting.rb2
-rw-r--r--app/models/status.rb6
-rw-r--r--app/models/stream_entry.rb2
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/models/user.rb80
-rw-r--r--app/models/web/push_subscription.rb4
-rw-r--r--app/models/web/setting.rb2
-rw-r--r--app/policies/account_moderation_note_policy.rb17
-rw-r--r--app/policies/account_policy.rb43
-rw-r--r--app/policies/application_policy.rb18
-rw-r--r--app/policies/custom_emoji_policy.rb31
-rw-r--r--app/policies/domain_block_policy.rb19
-rw-r--r--app/policies/email_domain_block_policy.rb15
-rw-r--r--app/policies/instance_policy.rb11
-rw-r--r--app/policies/report_policy.rb15
-rw-r--r--app/policies/settings_policy.rb11
-rw-r--r--app/policies/status_policy.rb39
-rw-r--r--app/policies/subscription_policy.rb7
-rw-r--r--app/policies/user_policy.rb41
-rw-r--r--app/serializers/initial_state_serializer.rb9
-rw-r--r--app/serializers/rest/instance_serializer.rb6
-rw-r--r--app/serializers/rest/list_serializer.rb5
-rw-r--r--app/services/activitypub/fetch_remote_status_service.rb6
-rw-r--r--app/services/batched_remove_status_service.rb13
-rw-r--r--app/services/fan_out_on_write_service.rb17
-rw-r--r--app/services/notify_service.rb57
-rw-r--r--app/services/post_status_service.rb4
-rw-r--r--app/services/process_mentions_service.rb29
-rw-r--r--app/services/remove_status_service.rb15
-rw-r--r--app/services/resolve_remote_account_service.rb4
-rw-r--r--app/services/suspend_account_service.rb25
-rw-r--r--app/validators/status_length_validator.rb2
-rw-r--r--app/views/accounts/_header.html.haml39
-rw-r--r--app/views/accounts/show.html.haml4
-rw-r--r--app/views/admin/account_moderation_notes/_account_moderation_note.html.haml2
-rw-r--r--app/views/admin/accounts/index.html.haml2
-rw-r--r--app/views/admin/accounts/show.html.haml43
-rw-r--r--app/views/admin/custom_emojis/_custom_emoji.html.haml7
-rw-r--r--app/views/home/index.html.haml10
-rwxr-xr-xapp/views/layouts/application.html.haml1
-rw-r--r--app/views/settings/applications/new.html.haml2
-rw-r--r--app/views/settings/applications/show.html.haml2
-rw-r--r--app/views/settings/notifications/show.html.haml3
-rw-r--r--app/views/user_mailer/confirmation_instructions.pt-BR.html.erb4
-rw-r--r--app/views/user_mailer/confirmation_instructions.pt-BR.text.erb4
-rw-r--r--app/views/user_mailer/confirmation_instructions.zh-cn.html.erb13
-rw-r--r--app/views/user_mailer/confirmation_instructions.zh-cn.text.erb12
-rw-r--r--app/views/user_mailer/password_change.pt-BR.html.erb2
-rw-r--r--app/views/user_mailer/password_change.pt-BR.text.erb2
-rw-r--r--app/views/user_mailer/password_change.zh-cn.html.erb4
-rw-r--r--app/views/user_mailer/password_change.zh-cn.text.erb4
-rw-r--r--app/views/user_mailer/reset_password_instructions.oc.html.erb2
-rw-r--r--app/views/user_mailer/reset_password_instructions.oc.text.erb2
-rw-r--r--app/views/user_mailer/reset_password_instructions.pt-BR.html.erb4
-rw-r--r--app/views/user_mailer/reset_password_instructions.pt-BR.text.erb4
-rw-r--r--app/views/user_mailer/reset_password_instructions.zh-cn.html.erb9
-rw-r--r--app/views/user_mailer/reset_password_instructions.zh-cn.text.erb9
-rw-r--r--app/workers/admin/suspension_worker.rb2
-rw-r--r--app/workers/feed_insert_worker.rb39
-rw-r--r--app/workers/push_update_worker.rb11
-rw-r--r--app/workers/thread_resolve_worker.rb6
-rwxr-xr-xbin/webpack36
-rwxr-xr-xbin/webpack-dev-server83
-rw-r--r--boxfile.yml82
-rw-r--r--config/brakeman.ignore56
-rw-r--r--config/deploy.rb2
-rw-r--r--config/i18n-tasks.yml2
-rw-r--r--config/initializers/paperclip.rb90
-rw-r--r--config/initializers/statsd.rb2
-rw-r--r--config/locales/activerecord.zh-CN.yml13
-rw-r--r--config/locales/devise.oc.yml2
-rw-r--r--config/locales/devise.zh-CN.yml68
-rw-r--r--config/locales/doorkeeper.pl.yml6
-rw-r--r--config/locales/doorkeeper.zh-CN.yml67
-rw-r--r--config/locales/en.yml19
-rw-r--r--config/locales/fr.yml2
-rw-r--r--config/locales/ja.yml28
-rw-r--r--config/locales/oc.yml13
-rw-r--r--config/locales/pl.yml45
-rw-r--r--config/locales/pt-BR.yml22
-rw-r--r--config/locales/simple_form.en.yml1
-rw-r--r--config/locales/simple_form.ja.yml1
-rw-r--r--config/locales/simple_form.oc.yml2
-rw-r--r--config/locales/simple_form.pl.yml1
-rw-r--r--config/locales/simple_form.zh-CN.yml60
-rw-r--r--config/locales/zh-CN.yml593
-rw-r--r--config/navigation.rb16
-rw-r--r--config/routes.rb23
-rw-r--r--config/settings.yml1
-rw-r--r--config/webpack/configuration.js6
-rw-r--r--config/webpack/production.js44
-rw-r--r--config/webpack/shared.js38
-rw-r--r--db/migrate/20171107143332_add_memorial_to_accounts.rb15
-rw-r--r--db/migrate/20171107143624_add_disabled_to_users.rb15
-rw-r--r--db/migrate/20171109012327_add_moderator_to_accounts.rb15
-rw-r--r--db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb8
-rw-r--r--db/migrate/20171114231651_create_lists.rb10
-rw-r--r--db/migrate/20171116161857_create_list_accounts.rb12
-rw-r--r--db/schema.rb31
-rw-r--r--lib/mastodon/migration_helpers.rb56
-rw-r--r--lib/tasks/mastodon.rake33
-rw-r--r--nanobox/nginx-web.conf.erb7
-rw-r--r--package.json52
-rw-r--r--public/sounds/boop.mp3bin12070 -> 12280 bytes
-rw-r--r--public/sounds/boop.oggbin5164 -> 5247 bytes
-rw-r--r--spec/controllers/accounts_controller_spec.rb139
-rw-r--r--spec/controllers/api/v1/lists/accounts_controller_spec.rb54
-rw-r--r--spec/controllers/api/v1/lists_controller_spec.rb68
-rw-r--r--spec/controllers/api/v1/timelines/list_controller_spec.rb56
-rw-r--r--spec/controllers/api/v1/timelines/tag_controller_spec.rb2
-rw-r--r--spec/controllers/settings/applications_controller_spec.rb10
-rw-r--r--spec/fabricators/list_account_fabricator.rb5
-rw-r--r--spec/fabricators/list_fabricator.rb4
-rw-r--r--spec/fabricators/session_activation_fabricator.rb2
-rw-r--r--spec/fabricators/setting_fabricator.rb4
-rw-r--r--spec/fixtures/requests/attachment1.txtbin192053 -> 192052 bytes
-rw-r--r--spec/fixtures/requests/attachment2.txtbin109078 -> 109077 bytes
-rw-r--r--spec/fixtures/requests/avatar.txtbin109962 -> 109961 bytes
-rw-r--r--spec/fixtures/requests/idn.txt14
-rw-r--r--spec/helpers/stream_entries_helper_spec.rb4
-rw-r--r--spec/lib/feed_manager_spec.rb92
-rw-r--r--spec/lib/settings/scoped_settings_spec.rb35
-rw-r--r--spec/lib/user_settings_decorator_spec.rb2
-rw-r--r--spec/models/account_moderation_note_spec.rb2
-rw-r--r--spec/models/account_spec.rb100
-rw-r--r--spec/models/concerns/account_interactions_spec.rb18
-rw-r--r--spec/models/custom_emoji_spec.rb29
-rw-r--r--spec/models/email_domain_block_spec.rb5
-rw-r--r--spec/models/follow_request_spec.rb32
-rw-r--r--spec/models/home_feed_spec.rb (renamed from spec/models/feed_spec.rb)4
-rw-r--r--spec/models/list_account_spec.rb5
-rw-r--r--spec/models/list_spec.rb5
-rw-r--r--spec/models/media_attachment_spec.rb77
-rw-r--r--spec/models/notification_spec.rb113
-rw-r--r--spec/models/remote_follow_spec.rb67
-rw-r--r--spec/models/remote_profile_spec.rb143
-rw-r--r--spec/models/session_activation_spec.rb124
-rw-r--r--spec/models/setting_spec.rb184
-rw-r--r--spec/models/site_upload_spec.rb8
-rw-r--r--spec/models/status_spec.rb53
-rw-r--r--spec/models/stream_entry_spec.rb115
-rw-r--r--spec/models/tag_spec.rb7
-rw-r--r--spec/models/user_spec.rb39
-rw-r--r--spec/policies/status_policy_spec.rb6
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb2
-rw-r--r--spec/services/after_block_service_spec.rb4
-rw-r--r--spec/services/batched_remove_status_service_spec.rb4
-rw-r--r--spec/services/fan_out_on_write_service_spec.rb4
-rw-r--r--spec/services/mute_service_spec.rb4
-rw-r--r--spec/services/notify_service_spec.rb33
-rw-r--r--spec/services/remove_status_service_spec.rb4
-rw-r--r--spec/support/matchers/model/model_have_error_on_field.rb2
-rw-r--r--spec/workers/feed_insert_worker_spec.rb16
-rw-r--r--streaming/index.js52
-rw-r--r--yarn.lock1607
294 files changed, 5448 insertions, 2572 deletions
diff --git a/.env.nanobox b/.env.nanobox
index 7920c47b9..48204a6bf 100644
--- a/.env.nanobox
+++ b/.env.nanobox
@@ -35,6 +35,17 @@ PAPERCLIP_SECRET=$PAPERCLIP_SECRET
 SECRET_KEY_BASE=$SECRET_KEY_BASE
 OTP_SECRET=$OTP_SECRET
 
+# VAPID keys (used for push notifications)
+# You can generate the keys using the following command (first is the private key, second is the public one)
+# You should only generate this once per instance. If you later decide to change it, all push subscription will
+# be invalidated, requiring the users to access the website again to resubscribe.
+#
+# Generate with `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`)
+#
+# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
+VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
+VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
+
 # Registrations
 # Single user mode will disable registrations and redirect frontpage to the first profile
 # SINGLE_USER_MODE=true
@@ -62,7 +73,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 #SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
 #SMTP_OPENSSL_VERIFY_MODE=peer
 #SMTP_ENABLE_STARTTLS_AUTO=true
-
+#SMTP_TLS=true
 
 # Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
 # PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
@@ -91,6 +102,23 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
 # S3_ENDPOINT=
 # S3_SIGNATURE_VERSION=
 
+# Swift (optional)
+# SWIFT_ENABLED=true
+# SWIFT_USERNAME=
+# For Keystone V3, the value for SWIFT_TENANT should be the project name
+# SWIFT_TENANT=
+# SWIFT_PASSWORD=
+# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
+# issues with token rate-limiting during high load.
+# SWIFT_AUTH_URL=
+# SWIFT_CONTAINER=
+# SWIFT_OBJECT_URL=
+# SWIFT_REGION=
+# Defaults to 'default'
+# SWIFT_DOMAIN_NAME=
+# Defaults to 60 seconds. Set to 0 to disable
+# SWIFT_CACHE_TTL=
+
 # Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
 # S3_CLOUDFRONT_HOST=
 
diff --git a/.env.production.sample b/.env.production.sample
index 91fcce6ac..27de27de4 100644
--- a/.env.production.sample
+++ b/.env.production.sample
@@ -134,3 +134,6 @@ STREAMING_CLUSTER_NUM=1
 # If you use Docker, you may want to assign UID/GID manually.
 # UID=1000
 # GID=1000
+
+# Maximum allowed character count
+# MAX_TOOT_CHARS=500
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..7cec57180
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/
diff --git a/Dockerfile b/Dockerfile
index c3b38fa8b..f9354ac46 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -48,7 +48,7 @@ RUN apk -U upgrade \
  && rm yarn.tar.gz \
  && mv /tmp/src/yarn-v$YARN_VERSION /opt/yarn \
  && ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
- && wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
+ && wget -O libiconv.tar.gz "https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
  && echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
  && tar -xzf libiconv.tar.gz -C /tmp/src \
  && rm libiconv.tar.gz \
diff --git a/Gemfile b/Gemfile
index 7b359af1d..d0b7aaef1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,8 +14,10 @@ gem 'pg', '~> 0.20'
 gem 'pghero', '~> 1.7'
 gem 'dotenv-rails', '~> 2.2'
 
-gem 'aws-sdk', '~> 2.9'
-gem 'fog-openstack', '~> 0.1'
+gem 'fog-aws', '~> 1.4', require: false
+gem 'fog-core', '~> 1.45'
+gem 'fog-local', '~> 0.4', require: false
+gem 'fog-openstack', '~> 0.1', require: false
 gem 'paperclip', '~> 5.1'
 gem 'paperclip-av-transcoder', '~> 0.6'
 
@@ -38,14 +40,14 @@ gem 'http', '~> 2.2'
 gem 'http_accept_language', '~> 2.1'
 gem 'httplog', '~> 0.99'
 gem 'idn-ruby', require: 'idn'
-gem 'kaminari', '~> 1.0'
+gem 'kaminari', '~> 1.1'
 gem 'link_header', '~> 0.0'
 gem 'mime-types', '~> 3.1'
-gem 'nokogiri', '~> 1.7'
+gem 'nokogiri', '~> 1.8'
 gem 'nsa', '~> 0.2'
-gem 'oj', '~> 3.0'
+gem 'oj', '~> 3.3'
 gem 'ostatus2', '~> 2.0'
-gem 'ox', '~> 2.5'
+gem 'ox', '~> 2.8'
 gem 'pundit', '~> 1.1'
 gem 'rabl', '~> 0.13'
 gem 'rack-attack', '~> 5.0'
@@ -75,15 +77,15 @@ gem 'json-ld-preloaded', '~> 2.2.1'
 gem 'rdf-normalize', '~> 0.3.1'
 
 group :development, :test do
-  gem 'fabrication', '~> 2.16'
+  gem 'fabrication', '~> 2.18'
   gem 'fuubar', '~> 2.2'
   gem 'i18n-tasks', '~> 0.9', require: false
   gem 'pry-rails', '~> 0.3'
-  gem 'rspec-rails', '~> 3.6'
+  gem 'rspec-rails', '~> 3.7'
 end
 
 group :test do
-  gem 'capybara', '~> 2.14'
+  gem 'capybara', '~> 2.15'
   gem 'climate_control', '~> 0.2'
   gem 'faker', '~> 1.7'
   gem 'microformats', '~> 4.0'
@@ -91,13 +93,13 @@ group :test do
   gem 'rspec-sidekiq', '~> 3.0'
   gem 'simplecov', '~> 0.14', require: false
   gem 'webmock', '~> 3.0'
-  gem 'parallel_tests', '~> 2.14'
+  gem 'parallel_tests', '~> 2.17'
 end
 
 group :development do
   gem 'active_record_query_trace', '~> 1.5'
   gem 'annotate', '~> 2.7'
-  gem 'better_errors', '~> 2.1'
+  gem 'better_errors', '~> 2.4'
   gem 'binding_of_caller', '~> 0.7'
   gem 'bullet', '~> 5.5'
   gem 'letter_opener', '~> 1.4'
@@ -105,15 +107,15 @@ group :development do
   gem 'rubocop', require: false
   gem 'brakeman', '~> 4.0', require: false
   gem 'bundler-audit', '~> 0.6', require: false
-  gem 'scss_lint', '~> 0.53', require: false
+  gem 'scss_lint', '~> 0.55', require: false
 
-  gem 'capistrano', '~> 3.8'
-  gem 'capistrano-rails', '~> 1.2'
+  gem 'capistrano', '~> 3.10'
+  gem 'capistrano-rails', '~> 1.3'
   gem 'capistrano-rbenv', '~> 2.1'
   gem 'capistrano-yarn', '~> 2.0'
 end
 
 group :production do
-  gem 'lograge', '~> 0.5'
+  gem 'lograge', '~> 0.7'
   gem 'redis-rails', '~> 5.0'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index 14ed0d309..f9c69d538 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -57,25 +57,17 @@ GEM
       encryptor (~> 3.0.0)
     av (0.9.0)
       cocaine (~> 0.5.3)
-    aws-sdk (2.10.46)
-      aws-sdk-resources (= 2.10.46)
-    aws-sdk-core (2.10.46)
-      aws-sigv4 (~> 1.0)
-      jmespath (~> 1.0)
-    aws-sdk-resources (2.10.46)
-      aws-sdk-core (= 2.10.46)
-    aws-sigv4 (1.0.2)
     bcrypt (3.1.11)
-    better_errors (2.3.0)
+    better_errors (2.4.0)
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
-    binding_of_caller (0.7.2)
+    binding_of_caller (0.7.3)
       debug_inspector (>= 0.0.1)
-    bootsnap (1.1.3)
+    bootsnap (1.1.5)
       msgpack (~> 1.0)
     brakeman (4.0.1)
-    browser (2.5.1)
+    browser (2.5.2)
     builder (3.2.3)
     bullet (5.6.1)
       activesupport (>= 3.0.0)
@@ -83,23 +75,23 @@ GEM
     bundler-audit (0.6.0)
       bundler (~> 1.2)
       thor (~> 0.18)
-    capistrano (3.9.1)
+    capistrano (3.10.0)
       airbrussh (>= 1.0.0)
       i18n
       rake (>= 10.0.0)
       sshkit (>= 1.9.0)
-    capistrano-bundler (1.2.0)
+    capistrano-bundler (1.3.0)
       capistrano (~> 3.1)
       sshkit (~> 1.2)
     capistrano-rails (1.3.0)
       capistrano (~> 3.1)
       capistrano-bundler (~> 1.1)
-    capistrano-rbenv (2.1.1)
+    capistrano-rbenv (2.1.2)
       capistrano (~> 3.1)
       sshkit (~> 1.3)
     capistrano-yarn (2.0.2)
       capistrano (~> 3.0)
-    capybara (2.15.1)
+    capybara (2.15.4)
       addressable
       mini_mime (>= 0.1.3)
       nokogiri (>= 1.3.3)
@@ -110,7 +102,7 @@ GEM
       activesupport
     charlock_holmes (0.7.5)
     chunky_png (1.3.8)
-    cld3 (3.2.0)
+    cld3 (3.2.1)
       ffi (>= 1.1.0, < 1.10.0)
     climate_control (0.2.0)
     cocaine (0.5.8)
@@ -150,16 +142,21 @@ GEM
       thread
       thread_safe
     encryptor (3.0.0)
-    erubi (1.6.1)
-    et-orbi (1.0.5)
+    erubi (1.7.0)
+    et-orbi (1.0.8)
       tzinfo
     excon (0.59.0)
     execjs (2.7.0)
-    fabrication (2.16.3)
+    fabrication (2.18.0)
     faker (1.8.4)
       i18n (~> 0.5)
     fast_blank (1.0.0)
     ffi (1.9.18)
+    fog-aws (1.4.1)
+      fog-core (~> 1.38)
+      fog-json (~> 1.0)
+      fog-xml (~> 0.1)
+      ipaddress (~> 0.8)
     fog-core (1.45.0)
       builder
       excon (~> 0.58)
@@ -167,15 +164,20 @@ GEM
     fog-json (1.0.2)
       fog-core (~> 1.0)
       multi_json (~> 1.10)
-    fog-openstack (0.1.21)
+    fog-local (0.4.0)
+      fog-core (~> 1.27)
+    fog-openstack (0.1.22)
       fog-core (>= 1.40)
       fog-json (>= 1.0)
       ipaddress (>= 0.8)
+    fog-xml (0.1.3)
+      fog-core
+      nokogiri (>= 1.5.11, < 2.0.0)
     formatador (0.2.5)
     fuubar (2.2.0)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
-    globalid (0.4.0)
+    globalid (0.4.1)
       activesupport (>= 4.2.0)
     goldfinger (2.0.1)
       addressable (~> 2.5)
@@ -211,7 +213,8 @@ GEM
     httplog (0.99.7)
       colorize
       rack
-    i18n (0.8.6)
+    i18n (0.9.0)
+      concurrent-ruby (~> 1.0)
     i18n-tasks (0.9.18)
       activesupport (>= 4.0.2)
       ast (>= 2.1.0)
@@ -225,29 +228,28 @@ GEM
     idn-ruby (0.1.0)
     ipaddress (0.8.3)
     iso-639 (0.2.8)
-    jmespath (1.3.1)
     json (2.1.0)
-    json-ld (2.1.5)
+    json-ld (2.1.7)
       multi_json (~> 1.12)
-      rdf (~> 2.2)
+      rdf (~> 2.2, >= 2.2.8)
     json-ld-preloaded (2.2.2)
       json-ld (~> 2.1, >= 2.1.5)
       multi_json (~> 1.11)
       rdf (~> 2.2)
     jsonapi-renderer (0.1.3)
     jwt (1.5.6)
-    kaminari (1.0.1)
+    kaminari (1.1.1)
       activesupport (>= 4.1.0)
-      kaminari-actionview (= 1.0.1)
-      kaminari-activerecord (= 1.0.1)
-      kaminari-core (= 1.0.1)
-    kaminari-actionview (1.0.1)
+      kaminari-actionview (= 1.1.1)
+      kaminari-activerecord (= 1.1.1)
+      kaminari-core (= 1.1.1)
+    kaminari-actionview (1.1.1)
       actionview
-      kaminari-core (= 1.0.1)
-    kaminari-activerecord (1.0.1)
+      kaminari-core (= 1.1.1)
+    kaminari-activerecord (1.1.1)
       activerecord
-      kaminari-core (= 1.0.1)
-    kaminari-core (1.0.1)
+      kaminari-core (= 1.1.1)
+    kaminari-core (1.1.1)
     launchy (2.4.3)
       addressable (~> 2.3)
     letter_opener (1.4.1)
@@ -257,18 +259,19 @@ GEM
       letter_opener (~> 1.0)
       railties (>= 3.2)
     link_header (0.0.8)
-    lograge (0.6.0)
+    lograge (0.7.1)
       actionpack (>= 4, < 5.2)
       activesupport (>= 4, < 5.2)
       railties (>= 4, < 5.2)
       request_store (~> 1.0)
-    loofah (2.0.3)
+    loofah (2.1.1)
+      crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.6.6)
       mime-types (>= 1.16, < 4)
     mario-redis-lock (1.2.0)
       redis (~> 3, >= 3.0.5)
-    method_source (0.8.2)
+    method_source (0.9.0)
     microformats (4.0.7)
       json
       nokogiri
@@ -277,7 +280,7 @@ GEM
     mime-types-data (3.2016.0521)
     mimemagic (0.3.2)
     mini_mime (0.1.4)
-    mini_portile2 (2.2.0)
+    mini_portile2 (2.3.0)
     minitest (5.10.3)
     msgpack (1.1.0)
     multi_json (1.12.2)
@@ -285,8 +288,8 @@ GEM
       net-ssh (>= 2.6.5)
     net-ssh (4.2.0)
     nio4r (2.1.0)
-    nokogiri (1.8.0)
-      mini_portile2 (~> 2.2.0)
+    nokogiri (1.8.1)
+      mini_portile2 (~> 2.3.0)
     nokogumbo (1.4.13)
       nokogiri
     nsa (0.2.4)
@@ -294,15 +297,15 @@ GEM
       concurrent-ruby (~> 1.0.0)
       sidekiq (>= 3.5.0)
       statsd-ruby (~> 1.2.0)
-    oj (3.3.5)
-    openssl (2.0.5)
+    oj (3.3.9)
+    openssl (2.0.6)
     orm_adapter (0.5.0)
     ostatus2 (2.0.1)
       addressable (~> 2.4)
       http (~> 2.0)
       nokogiri (~> 1.6)
       openssl (~> 2.0)
-    ox (2.6.0)
+    ox (2.8.1)
     paperclip (5.1.0)
       activemodel (>= 4.2.0)
       activesupport (>= 4.2.0)
@@ -313,19 +316,18 @@ GEM
       av (~> 0.9.0)
       paperclip (>= 2.5.2)
     parallel (1.12.0)
-    parallel_tests (2.15.0)
+    parallel_tests (2.17.0)
       parallel
     parser (2.4.0.0)
       ast (~> 2.2)
     pg (0.21.0)
     pghero (1.7.0)
       activerecord
-    pkg-config (1.2.7)
+    pkg-config (1.2.8)
     powerpack (0.1.1)
-    pry (0.10.4)
+    pry (0.11.2)
       coderay (~> 1.1.0)
-      method_source (~> 0.8.1)
-      slop (~> 3.4)
+      method_source (~> 0.9.0)
     pry-rails (0.3.6)
       pry (>= 0.10.4)
     public_suffix (3.0.0)
@@ -379,31 +381,31 @@ GEM
       thor (>= 0.18.1, < 2.0)
     rainbow (2.2.2)
       rake
-    rake (12.1.0)
-    rdf (2.2.9)
+    rake (12.2.1)
+    rdf (2.2.11)
       hamster (~> 3.0)
       link_header (~> 0.0, >= 0.0.8)
     rdf-normalize (0.3.2)
       rdf (~> 2.0)
-    redis (3.3.3)
-    redis-actionpack (5.0.1)
+    redis (3.3.5)
+    redis-actionpack (5.0.2)
       actionpack (>= 4.0, < 6)
       redis-rack (>= 1, < 3)
-      redis-store (>= 1.1.0, < 1.4.0)
-    redis-activesupport (5.0.3)
+      redis-store (>= 1.1.0, < 2)
+    redis-activesupport (5.0.4)
       activesupport (>= 3, < 6)
-      redis-store (~> 1.3.0)
+      redis-store (>= 1.3, < 2)
     redis-namespace (1.5.3)
       redis (~> 3.0, >= 3.0.4)
-    redis-rack (2.0.2)
+    redis-rack (2.0.3)
       rack (>= 1.5, < 3)
-      redis-store (>= 1.2, < 1.4)
+      redis-store (>= 1.2, < 2)
     redis-rails (5.0.2)
       redis-actionpack (>= 5.0, < 6)
       redis-activesupport (>= 5.0, < 6)
       redis-store (>= 1.2, < 2)
-    redis-store (1.3.0)
-      redis (>= 2.2)
+    redis-store (1.4.1)
+      redis (>= 2.2, < 5)
     request_store (1.3.2)
     responders (2.4.0)
       actionpack (>= 4.2.0, < 5.3)
@@ -411,27 +413,27 @@ GEM
     rotp (2.1.2)
     rqrcode (0.10.1)
       chunky_png (~> 1.0)
-    rspec-core (3.6.0)
-      rspec-support (~> 3.6.0)
-    rspec-expectations (3.6.0)
+    rspec-core (3.7.0)
+      rspec-support (~> 3.7.0)
+    rspec-expectations (3.7.0)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.6.0)
-    rspec-mocks (3.6.0)
+      rspec-support (~> 3.7.0)
+    rspec-mocks (3.7.0)
       diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.6.0)
-    rspec-rails (3.6.1)
+      rspec-support (~> 3.7.0)
+    rspec-rails (3.7.1)
       actionpack (>= 3.0)
       activesupport (>= 3.0)
       railties (>= 3.0)
-      rspec-core (~> 3.6.0)
-      rspec-expectations (~> 3.6.0)
-      rspec-mocks (~> 3.6.0)
-      rspec-support (~> 3.6.0)
+      rspec-core (~> 3.7.0)
+      rspec-expectations (~> 3.7.0)
+      rspec-mocks (~> 3.7.0)
+      rspec-support (~> 3.7.0)
     rspec-sidekiq (3.0.3)
       rspec-core (~> 3.0, >= 3.0.0)
       sidekiq (>= 2.4.0)
-    rspec-support (3.6.0)
-    rubocop (0.50.0)
+    rspec-support (3.7.0)
+    rubocop (0.51.0)
       parallel (~> 1.10)
       parser (>= 2.3.3.1, < 3.0)
       powerpack (~> 0.1)
@@ -439,7 +441,7 @@ GEM
       ruby-progressbar (~> 1.7)
       unicode-display_width (~> 1.0, >= 1.0.1)
     ruby-oembed (0.12.0)
-    ruby-progressbar (1.8.3)
+    ruby-progressbar (1.9.0)
     rufus-scheduler (3.4.2)
       et-orbi (~> 1.0)
     safe_yaml (1.0.4)
@@ -448,19 +450,19 @@ GEM
       nokogiri (>= 1.4.4)
       nokogumbo (~> 1.4.1)
     sass (3.4.25)
-    scss_lint (0.54.0)
+    scss_lint (0.55.0)
       rake (>= 0.9, < 13)
       sass (~> 3.4.20)
-    sidekiq (5.0.4)
+    sidekiq (5.0.5)
       concurrent-ruby (~> 1.0)
       connection_pool (~> 2.2, >= 2.2.0)
       rack-protection (>= 1.5.0)
-      redis (~> 3.3, >= 3.3.3)
+      redis (>= 3.3.4, < 5)
     sidekiq-bulk (0.1.1)
       activesupport
       sidekiq
-    sidekiq-scheduler (2.1.9)
-      redis (~> 3)
+    sidekiq-scheduler (2.1.10)
+      redis (>= 3, < 5)
       rufus-scheduler (~> 3.2)
       sidekiq (>= 3)
       tilt (>= 1.4.0)
@@ -477,7 +479,6 @@ GEM
       json (>= 1.8, < 3)
       simplecov-html (~> 0.10.0)
     simplecov-html (0.10.2)
-    slop (3.6.0)
     sprockets (3.7.1)
       concurrent-ruby (~> 1.0)
       rack (> 1, < 3)
@@ -500,9 +501,9 @@ GEM
     tilt (2.0.8)
     twitter-text (1.14.7)
       unf (~> 0.1.0)
-    tzinfo (1.2.3)
+    tzinfo (1.2.4)
       thread_safe (~> 0.1)
-    tzinfo-data (1.2017.2)
+    tzinfo-data (1.2017.3)
       tzinfo (>= 1.0.0)
     uglifier (3.2.0)
       execjs (>= 0.3.0, < 3)
@@ -517,7 +518,7 @@ GEM
       addressable (>= 2.3.6)
       crack (>= 0.3.2)
       hashdiff
-    webpacker (3.0.1)
+    webpacker (3.0.2)
       activesupport (>= 4.2)
       rack-proxy (>= 0.6.1)
       railties (>= 4.2)
@@ -538,19 +539,18 @@ DEPENDENCIES
   active_record_query_trace (~> 1.5)
   addressable (~> 2.5)
   annotate (~> 2.7)
-  aws-sdk (~> 2.9)
-  better_errors (~> 2.1)
+  better_errors (~> 2.4)
   binding_of_caller (~> 0.7)
   bootsnap
   brakeman (~> 4.0)
   browser
   bullet (~> 5.5)
   bundler-audit (~> 0.6)
-  capistrano (~> 3.8)
-  capistrano-rails (~> 1.2)
+  capistrano (~> 3.10)
+  capistrano-rails (~> 1.3)
   capistrano-rbenv (~> 2.1)
   capistrano-yarn (~> 2.0)
-  capybara (~> 2.14)
+  capybara (~> 2.15)
   charlock_holmes (~> 0.7.5)
   cld3 (~> 3.2.0)
   climate_control (~> 0.2)
@@ -558,9 +558,12 @@ DEPENDENCIES
   devise-two-factor (~> 3.0)
   doorkeeper (~> 4.2)
   dotenv-rails (~> 2.2)
-  fabrication (~> 2.16)
+  fabrication (~> 2.18)
   faker (~> 1.7)
   fast_blank (~> 1.0)
+  fog-aws (~> 1.4)
+  fog-core (~> 1.45)
+  fog-local (~> 0.4)
   fog-openstack (~> 0.1)
   fuubar (~> 2.2)
   goldfinger (~> 2.0)
@@ -574,22 +577,22 @@ DEPENDENCIES
   idn-ruby
   iso-639
   json-ld-preloaded (~> 2.2.1)
-  kaminari (~> 1.0)
+  kaminari (~> 1.1)
   letter_opener (~> 1.4)
   letter_opener_web (~> 1.3)
   link_header (~> 0.0)
-  lograge (~> 0.5)
+  lograge (~> 0.7)
   mario-redis-lock (~> 1.2)
   microformats (~> 4.0)
   mime-types (~> 3.1)
-  nokogiri (~> 1.7)
+  nokogiri (~> 1.8)
   nsa (~> 0.2)
-  oj (~> 3.0)
+  oj (~> 3.3)
   ostatus2 (~> 2.0)
-  ox (~> 2.5)
+  ox (~> 2.8)
   paperclip (~> 5.1)
   paperclip-av-transcoder (~> 0.6)
-  parallel_tests (~> 2.14)
+  parallel_tests (~> 2.17)
   pg (~> 0.20)
   pghero (~> 1.7)
   pkg-config (~> 1.2)
@@ -609,12 +612,12 @@ DEPENDENCIES
   redis-namespace (~> 1.5)
   redis-rails (~> 5.0)
   rqrcode (~> 0.10)
-  rspec-rails (~> 3.6)
+  rspec-rails (~> 3.7)
   rspec-sidekiq (~> 3.0)
   rubocop
   ruby-oembed (~> 0.12)
   sanitize (~> 4.4)
-  scss_lint (~> 0.53)
+  scss_lint (~> 0.55)
   sidekiq (~> 5.0)
   sidekiq-bulk (~> 0.1.1)
   sidekiq-scheduler (~> 2.1)
diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb
index 414a875d0..7f69a3363 100644
--- a/app/controllers/admin/account_moderation_notes_controller.rb
+++ b/app/controllers/admin/account_moderation_notes_controller.rb
@@ -1,31 +1,41 @@
 # frozen_string_literal: true
 
-class Admin::AccountModerationNotesController < Admin::BaseController
-  def create
-    @account_moderation_note = current_account.account_moderation_notes.new(resource_params)
-    if @account_moderation_note.save
-      @target_account = @account_moderation_note.target_account
-      redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg')
-    else
-      @account = @account_moderation_note.target_account
-      @moderation_notes = @account.targeted_moderation_notes.latest
-      render template: 'admin/accounts/show'
+module Admin
+  class AccountModerationNotesController < BaseController
+    before_action :set_account_moderation_note, only: [:destroy]
+
+    def create
+      authorize AccountModerationNote, :create?
+
+      @account_moderation_note = current_account.account_moderation_notes.new(resource_params)
+
+      if @account_moderation_note.save
+        redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.created_msg')
+      else
+        @account          = @account_moderation_note.target_account
+        @moderation_notes = @account.targeted_moderation_notes.latest
+
+        render template: 'admin/accounts/show'
+      end
     end
-  end
 
-  def destroy
-    @account_moderation_note = AccountModerationNote.find(params[:id])
-    @target_account = @account_moderation_note.target_account
-    @account_moderation_note.destroy
-    redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
-  end
+    def destroy
+      authorize @account_moderation_note, :destroy?
+      @account_moderation_note.destroy
+      redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
+    end
 
-  private
+    private
 
-  def resource_params
-    params.require(:account_moderation_note).permit(
-      :content,
-      :target_account_id
-    )
+    def resource_params
+      params.require(:account_moderation_note).permit(
+        :content,
+        :target_account_id
+      )
+    end
+
+    def set_account_moderation_note
+      @account_moderation_note = AccountModerationNote.find(params[:id])
+    end
   end
 end
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index ffa4dc850..0829bc769 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -2,29 +2,54 @@
 
 module Admin
   class AccountsController < BaseController
-    before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload]
+    before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize]
     before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
+    before_action :require_local_account!, only: [:enable, :disable, :memorialize]
 
     def index
+      authorize :account, :index?
       @accounts = filtered_accounts.page(params[:page])
     end
 
     def show
+      authorize @account, :show?
       @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
       @moderation_notes = @account.targeted_moderation_notes.latest
     end
 
     def subscribe
+      authorize @account, :subscribe?
       Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
       redirect_to admin_account_path(@account.id)
     end
 
     def unsubscribe
+      authorize @account, :unsubscribe?
       Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
       redirect_to admin_account_path(@account.id)
     end
 
+    def memorialize
+      authorize @account, :memorialize?
+      @account.memorialize!
+      redirect_to admin_account_path(@account.id)
+    end
+
+    def enable
+      authorize @account.user, :enable?
+      @account.user.enable!
+      redirect_to admin_account_path(@account.id)
+    end
+
+    def disable
+      authorize @account.user, :disable?
+      @account.user.disable!
+      redirect_to admin_account_path(@account.id)
+    end
+
     def redownload
+      authorize @account, :redownload?
+
       @account.reset_avatar!
       @account.reset_header!
       @account.save!
@@ -42,6 +67,10 @@ module Admin
       redirect_to admin_account_path(@account.id) if @account.local?
     end
 
+    def require_local_account!
+      redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
+    end
+
     def filtered_accounts
       AccountFilter.new(filter_params).results
     end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
index 11fe326bc..db4839a8f 100644
--- a/app/controllers/admin/base_controller.rb
+++ b/app/controllers/admin/base_controller.rb
@@ -2,7 +2,9 @@
 
 module Admin
   class BaseController < ApplicationController
-    before_action :require_admin!
+    include Authorization
+
+    before_action :require_staff!
 
     layout 'admin'
   end
diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb
index 2542e21ee..c10b0ebee 100644
--- a/app/controllers/admin/confirmations_controller.rb
+++ b/app/controllers/admin/confirmations_controller.rb
@@ -2,15 +2,18 @@
 
 module Admin
   class ConfirmationsController < BaseController
+    before_action :set_user
+
     def create
-      account_user.confirm
+      authorize @user, :confirm?
+      @user.confirm!
       redirect_to admin_accounts_path
     end
 
     private
 
-    def account_user
-      Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
+    def set_user
+      @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
     end
   end
 end
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
index cbd7abe95..509f7a48f 100644
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ b/app/controllers/admin/custom_emojis_controller.rb
@@ -5,14 +5,18 @@ module Admin
     before_action :set_custom_emoji, except: [:index, :new, :create]
 
     def index
-      @custom_emojis = filtered_custom_emojis.page(params[:page])
+      authorize :custom_emoji, :index?
+      @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
     end
 
     def new
+      authorize :custom_emoji, :create?
       @custom_emoji = CustomEmoji.new
     end
 
     def create
+      authorize :custom_emoji, :create?
+
       @custom_emoji = CustomEmoji.new(resource_params)
 
       if @custom_emoji.save
@@ -23,6 +27,8 @@ module Admin
     end
 
     def update
+      authorize @custom_emoji, :update?
+
       if @custom_emoji.update(resource_params)
         redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
       else
@@ -31,12 +37,16 @@ module Admin
     end
 
     def destroy
+      authorize @custom_emoji, :destroy?
       @custom_emoji.destroy
       redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
     end
 
     def copy
-      emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image)
+      authorize @custom_emoji, :copy?
+
+      emoji = CustomEmoji.find_or_initialize_by(domain: nil, shortcode: @custom_emoji.shortcode)
+      emoji.image = @custom_emoji.image
 
       if emoji.save
         flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
@@ -48,11 +58,13 @@ module Admin
     end
 
     def enable
+      authorize @custom_emoji, :enable?
       @custom_emoji.update!(disabled: false)
       redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
     end
 
     def disable
+      authorize @custom_emoji, :disable?
       @custom_emoji.update!(disabled: true)
       redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
     end
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 1ab620e03..e383dc831 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -5,14 +5,18 @@ module Admin
     before_action :set_domain_block, only: [:show, :destroy]
 
     def index
+      authorize :domain_block, :index?
       @domain_blocks = DomainBlock.page(params[:page])
     end
 
     def new
+      authorize :domain_block, :create?
       @domain_block = DomainBlock.new
     end
 
     def create
+      authorize :domain_block, :create?
+
       @domain_block = DomainBlock.new(resource_params)
 
       if @domain_block.save
@@ -23,9 +27,12 @@ module Admin
       end
     end
 
-    def show; end
+    def show
+      authorize @domain_block, :show?
+    end
 
     def destroy
+      authorize @domain_block, :destroy?
       UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
       redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
     end
diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb
index 09275d5dc..01058bf46 100644
--- a/app/controllers/admin/email_domain_blocks_controller.rb
+++ b/app/controllers/admin/email_domain_blocks_controller.rb
@@ -5,14 +5,18 @@ module Admin
     before_action :set_email_domain_block, only: [:show, :destroy]
 
     def index
+      authorize :email_domain_block, :index?
       @email_domain_blocks = EmailDomainBlock.page(params[:page])
     end
 
     def new
+      authorize :email_domain_block, :create?
       @email_domain_block = EmailDomainBlock.new
     end
 
     def create
+      authorize :email_domain_block, :create?
+
       @email_domain_block = EmailDomainBlock.new(resource_params)
 
       if @email_domain_block.save
@@ -23,6 +27,7 @@ module Admin
     end
 
     def destroy
+      authorize @email_domain_block, :destroy?
       @email_domain_block.destroy
       redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
     end
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index 22f02e5d0..8ed0ea421 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -3,10 +3,12 @@
 module Admin
   class InstancesController < BaseController
     def index
+      authorize :instance, :index?
       @instances = ordered_instances
     end
 
     def resubscribe
+      authorize :instance, :resubscribe?
       params.require(:by_domain)
       Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
       redirect_to admin_instances_path
diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb
index 5a31adecf..4f66ce708 100644
--- a/app/controllers/admin/reported_statuses_controller.rb
+++ b/app/controllers/admin/reported_statuses_controller.rb
@@ -2,19 +2,20 @@
 
 module Admin
   class ReportedStatusesController < BaseController
-    include Authorization
-
     before_action :set_report
     before_action :set_status, only: [:update, :destroy]
 
     def create
-      @form = Form::StatusBatch.new(form_status_batch_params)
-      flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
+      authorize :status, :update?
+
+      @form         = Form::StatusBatch.new(form_status_batch_params)
+      flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
 
       redirect_to admin_report_path(@report)
     end
 
     def update
+      authorize @status, :update?
       @status.update(status_params)
       redirect_to admin_report_path(@report)
     end
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 226467739..745757ee8 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -5,14 +5,17 @@ module Admin
     before_action :set_report, except: [:index]
 
     def index
+      authorize :report, :index?
       @reports = filtered_reports.page(params[:page])
     end
 
     def show
+      authorize @report, :show?
       @form = Form::StatusBatch.new
     end
 
     def update
+      authorize @report, :update?
       process_report
       redirect_to admin_report_path(@report)
     end
diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb
index 6db648403..00b590bf6 100644
--- a/app/controllers/admin/resets_controller.rb
+++ b/app/controllers/admin/resets_controller.rb
@@ -2,17 +2,18 @@
 
 module Admin
   class ResetsController < BaseController
-    before_action :set_account
+    before_action :set_user
 
     def create
-      @account.user.send_reset_password_instructions
+      authorize @user, :reset_password?
+      @user.send_reset_password_instructions
       redirect_to admin_accounts_path
     end
 
     private
 
-    def set_account
-      @account = Account.find(params[:account_id])
+    def set_user
+      @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
     end
   end
 end
diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb
new file mode 100644
index 000000000..8f8685827
--- /dev/null
+++ b/app/controllers/admin/roles_controller.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Admin
+  class RolesController < BaseController
+    before_action :set_user
+
+    def promote
+      authorize @user, :promote?
+      @user.promote!
+      redirect_to admin_account_path(@user.account_id)
+    end
+
+    def demote
+      authorize @user, :demote?
+      @user.demote!
+      redirect_to admin_account_path(@user.account_id)
+    end
+
+    private
+
+    def set_user
+      @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
+    end
+  end
+end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index a2f86b8a9..e81290228 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -28,10 +28,13 @@ module Admin
     ).freeze
 
     def edit
+      authorize :settings, :show?
       @admin_settings = Form::AdminSettings.new
     end
 
     def update
+      authorize :settings, :update?
+
       settings_params.each do |key, value|
         if UPLOAD_SETTINGS.include?(key)
           upload = SiteUpload.where(var: key).first_or_initialize(var: key)
diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb
index 81a3008b9..01fb292de 100644
--- a/app/controllers/admin/silences_controller.rb
+++ b/app/controllers/admin/silences_controller.rb
@@ -5,11 +5,13 @@ module Admin
     before_action :set_account
 
     def create
+      authorize @account, :silence?
       @account.update(silenced: true)
       redirect_to admin_accounts_path
     end
 
     def destroy
+      authorize @account, :unsilence?
       @account.update(silenced: false)
       redirect_to admin_accounts_path
     end
diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb
index b05000b16..b54a9b824 100644
--- a/app/controllers/admin/statuses_controller.rb
+++ b/app/controllers/admin/statuses_controller.rb
@@ -2,8 +2,6 @@
 
 module Admin
   class StatusesController < BaseController
-    include Authorization
-
     helper_method :current_params
 
     before_action :set_account
@@ -12,24 +10,30 @@ module Admin
     PER_PAGE = 20
 
     def index
+      authorize :status, :index?
+
       @statuses = @account.statuses
+
       if params[:media]
         account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
         @statuses.merge!(Status.where(id: account_media_status_ids))
       end
-      @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
 
-      @form = Form::StatusBatch.new
+      @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
+      @form     = Form::StatusBatch.new
     end
 
     def create
-      @form = Form::StatusBatch.new(form_status_batch_params)
-      flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
+      authorize :status, :update?
+
+      @form         = Form::StatusBatch.new(form_status_batch_params)
+      flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
 
       redirect_to admin_account_statuses_path(@account.id, current_params)
     end
 
     def update
+      authorize @status, :update?
       @status.update(status_params)
       redirect_to admin_account_statuses_path(@account.id, current_params)
     end
@@ -60,6 +64,7 @@ module Admin
 
     def current_params
       page = (params[:page] || 1).to_i
+
       {
         media: params[:media],
         page: page > 1 && page,
diff --git a/app/controllers/admin/subscriptions_controller.rb b/app/controllers/admin/subscriptions_controller.rb
index 624a475a3..40500ef43 100644
--- a/app/controllers/admin/subscriptions_controller.rb
+++ b/app/controllers/admin/subscriptions_controller.rb
@@ -3,6 +3,7 @@
 module Admin
   class SubscriptionsController < BaseController
     def index
+      authorize :subscription, :index?
       @subscriptions = ordered_subscriptions.page(requested_page)
     end
 
diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb
index 5d9048d94..778feea5e 100644
--- a/app/controllers/admin/suspensions_controller.rb
+++ b/app/controllers/admin/suspensions_controller.rb
@@ -5,12 +5,14 @@ module Admin
     before_action :set_account
 
     def create
+      authorize @account, :suspend?
       Admin::SuspensionWorker.perform_async(@account.id)
       redirect_to admin_accounts_path
     end
 
     def destroy
-      @account.update(suspended: false)
+      authorize @account, :unsuspend?
+      @account.unsuspend!
       redirect_to admin_accounts_path
     end
 
diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb
index 69c08f605..5a45d25cd 100644
--- a/app/controllers/admin/two_factor_authentications_controller.rb
+++ b/app/controllers/admin/two_factor_authentications_controller.rb
@@ -5,6 +5,7 @@ module Admin
     before_action :set_user
 
     def destroy
+      authorize @user, :disable_2fa?
       @user.disable_two_factor!
       redirect_to admin_accounts_path
     end
diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb
new file mode 100644
index 000000000..40c485e8d
--- /dev/null
+++ b/app/controllers/api/v1/lists/accounts_controller.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+class Api::V1::Lists::AccountsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read },    only: [:show]
+  before_action -> { doorkeeper_authorize! :write }, except: [:show]
+
+  before_action :require_user!
+  before_action :set_list
+
+  after_action :insert_pagination_headers, only: :show
+
+  def show
+    @accounts = @list.accounts.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
+    render json: @accounts, each_serializer: REST::AccountSerializer
+  end
+
+  def create
+    ApplicationRecord.transaction do
+      list_accounts.each do |account|
+        @list.accounts << account
+      end
+    end
+
+    render_empty
+  end
+
+  def destroy
+    ListAccount.where(list: @list, account_id: account_ids).destroy_all
+    render_empty
+  end
+
+  private
+
+  def set_list
+    @list = List.where(account: current_account).find(params[:list_id])
+  end
+
+  def list_accounts
+    Account.find(account_ids)
+  end
+
+  def account_ids
+    Array(resource_params[:account_ids])
+  end
+
+  def resource_params
+    params.permit(account_ids: [])
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    if records_continue?
+      api_v1_list_accounts_url pagination_params(max_id: pagination_max_id)
+    end
+  end
+
+  def prev_path
+    unless @accounts.empty?
+      api_v1_list_accounts_url pagination_params(since_id: pagination_since_id)
+    end
+  end
+
+  def pagination_max_id
+    @accounts.last.id
+  end
+
+  def pagination_since_id
+    @accounts.first.id
+  end
+
+  def records_continue?
+    @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.permit(:limit).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb
new file mode 100644
index 000000000..9437373bd
--- /dev/null
+++ b/app/controllers/api/v1/lists_controller.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+class Api::V1::ListsController < Api::BaseController
+  LISTS_LIMIT = 50
+
+  before_action -> { doorkeeper_authorize! :read },    only: [:index, :show]
+  before_action -> { doorkeeper_authorize! :write }, except: [:index, :show]
+
+  before_action :require_user!
+  before_action :set_list, except: [:index, :create]
+
+  after_action :insert_pagination_headers, only: :index
+
+  def index
+    @lists = List.where(account: current_account).paginate_by_max_id(limit_param(LISTS_LIMIT), params[:max_id], params[:since_id])
+    render json: @lists, each_serializer: REST::ListSerializer
+  end
+
+  def show
+    render json: @list, serializer: REST::ListSerializer
+  end
+
+  def create
+    @list = List.create!(list_params.merge(account: current_account))
+    render json: @list, serializer: REST::ListSerializer
+  end
+
+  def update
+    @list.update!(list_params)
+    render json: @list, serializer: REST::ListSerializer
+  end
+
+  def destroy
+    @list.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_list
+    @list = List.where(account: current_account).find(params[:id])
+  end
+
+  def list_params
+    params.permit(:title)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    if records_continue?
+      api_v1_lists_url pagination_params(max_id: pagination_max_id)
+    end
+  end
+
+  def prev_path
+    unless @lists.empty?
+      api_v1_lists_url pagination_params(since_id: pagination_since_id)
+    end
+  end
+
+  def pagination_max_id
+    @lists.last.id
+  end
+
+  def pagination_since_id
+    @lists.first.id
+  end
+
+  def records_continue?
+    @lists.size == limit_param(LISTS_LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.permit(:limit).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index 9592cd4bd..22828217d 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController
       comment: report_params[:comment]
     )
 
-    User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
+    User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
 
     render json: @report, serializer: REST::ReportSerializer
   end
diff --git a/app/controllers/api/v1/search_controller.rb b/app/controllers/api/v1/search_controller.rb
index e183a71d7..d1b4e0402 100644
--- a/app/controllers/api/v1/search_controller.rb
+++ b/app/controllers/api/v1/search_controller.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class Api::V1::SearchController < Api::BaseController
+  include Authorization
+
   RESULTS_LIMIT = 10
 
   before_action -> { doorkeeper_authorize! :read }
@@ -9,12 +11,24 @@ class Api::V1::SearchController < Api::BaseController
   respond_to :json
 
   def index
-    @search = Search.new(search_results)
+    @search = Search.new(search)
     render json: @search, serializer: REST::SearchSerializer
   end
 
   private
 
+  def search
+    search_results.tap do |search|
+      search[:statuses].keep_if do |status|
+        begin
+          authorize status, :show?
+        rescue Mastodon::NotPermittedError
+          false
+        end
+      end
+    end
+  end
+
   def search_results
     SearchService.new.call(
       params[:q],
diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb
index 3dd27710c..db6cd8568 100644
--- a/app/controllers/api/v1/timelines/home_controller.rb
+++ b/app/controllers/api/v1/timelines/home_controller.rb
@@ -31,7 +31,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
   end
 
   def account_home_feed
-    Feed.new(:home, current_account)
+    HomeFeed.new(current_account)
   end
 
   def insert_pagination_headers
diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb
new file mode 100644
index 000000000..f5db71e46
--- /dev/null
+++ b/app/controllers/api/v1/timelines/list_controller.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+class Api::V1::Timelines::ListController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read }
+  before_action :require_user!
+  before_action :set_list
+  before_action :set_statuses
+
+  after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
+
+  def show
+    render json: @statuses,
+           each_serializer: REST::StatusSerializer,
+           relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id)
+  end
+
+  private
+
+  def set_list
+    @list = List.where(account: current_account).find(params[:id])
+  end
+
+  def set_statuses
+    @statuses = cached_list_statuses
+  end
+
+  def cached_list_statuses
+    cache_collection list_statuses, Status
+  end
+
+  def list_statuses
+    list_feed.get(
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def list_feed
+    ListFeed.new(@list)
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def pagination_params(core_params)
+    params.permit(:limit).merge(core_params)
+  end
+
+  def next_path
+    api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id)
+  end
+
+  def prev_path
+    api_v1_timelines_list_url params[:id], pagination_params(since_id: pagination_since_id)
+  end
+
+  def pagination_max_id
+    @statuses.last.id
+  end
+
+  def pagination_since_id
+    @statuses.first.id
+  end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d5eca6ffb..f5dbe837e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -13,11 +13,13 @@ class ApplicationController < ActionController::Base
   helper_method :current_account
   helper_method :current_session
   helper_method :current_theme
+  helper_method :theme_data
   helper_method :single_user_mode?
 
   rescue_from ActionController::RoutingError, with: :not_found
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
   rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
+  rescue_from Mastodon::NotPermittedError, with: :forbidden
 
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
   before_action :check_suspension, if: :user_signed_in?
@@ -40,6 +42,10 @@ class ApplicationController < ActionController::Base
     redirect_to root_path unless current_user&.admin?
   end
 
+  def require_staff!
+    redirect_to root_path unless current_user&.staff?
+  end
+
   def check_suspension
     forbidden if current_user.account.suspended?
   end
@@ -83,6 +89,10 @@ class ApplicationController < ActionController::Base
     current_user.setting_theme
   end
 
+  def theme_data
+    Themes.instance.get(current_theme)
+  end
+
   def cache_collection(raw, klass)
     return raw unless klass.respond_to?(:with_includes)
 
@@ -99,7 +109,7 @@ class ApplicationController < ActionController::Base
     unless uncached_ids.empty?
       uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
 
-      uncached.values.each do |item|
+      uncached.each_value do |item|
         Rails.cache.write(item.cache_key, item)
       end
     end
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 463a183e4..a5acb6c36 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -62,7 +62,7 @@ class Auth::SessionsController < Devise::SessionsController
 
     if user_params[:otp_attempt].present? && session[:otp_user_id]
       authenticate_with_two_factor_via_otp(user)
-    elsif user && user.valid_password?(user_params[:password])
+    elsif user&.valid_password?(user_params[:password])
       prompt_for_two_factor(user)
     end
   end
diff --git a/app/controllers/concerns/authorization.rb b/app/controllers/concerns/authorization.rb
index 7828fe48d..95a37e379 100644
--- a/app/controllers/concerns/authorization.rb
+++ b/app/controllers/concerns/authorization.rb
@@ -2,6 +2,7 @@
 
 module Authorization
   extend ActiveSupport::Concern
+
   include Pundit
 
   def pundit_user
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index ad7f09f34..21dde20ce 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -6,7 +6,6 @@ class HomeController < ApplicationController
 
   def index
     @body_classes = 'app-body'
-    @frontend     = (params[:frontend] and Rails.configuration.x.available_frontends.include? params[:frontend] + '.js') ? params[:frontend] : 'mastodon'
   end
 
   private
diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb
index 09839f16e..ce2530c54 100644
--- a/app/controllers/settings/notifications_controller.rb
+++ b/app/controllers/settings/notifications_controller.rb
@@ -26,7 +26,7 @@ class Settings::NotificationsController < ApplicationController
   def user_settings_params
     params.require(:user).permit(
       notification_emails: %i(follow follow_request reblog favourite mention digest),
-      interactions: %i(must_be_follower must_be_following)
+      interactions: %i(must_be_follower must_be_following must_be_following_dm)
     )
   end
 end
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 6a57b3d63..e0fae9d9a 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -18,7 +18,7 @@ module Admin::FilterHelper
 
   def selected?(more_params)
     new_url = filtered_url_for(more_params)
-    filter_link_class(new_url) == 'selected' ? true : false
+    filter_link_class(new_url) == 'selected'
   end
 
   private
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 6d625e7db..7dfab1df1 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -35,6 +35,11 @@ module ApplicationHelper
     Rails.env.production? ? site_title : "#{site_title} (Dev)"
   end
 
+  def can?(action, record)
+    return false if record.nil?
+    policy(record).public_send("#{action}?")
+  end
+
   def fa_icon(icon, attributes = {})
     class_names = attributes[:class]&.split(' ') || []
     class_names << 'fa'
@@ -43,6 +48,10 @@ module ApplicationHelper
     content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
   end
 
+  def custom_emoji_tag(custom_emoji)
+    image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
+  end
+
   def opengraph(property, content)
     tag(:meta, content: content, property: property)
   end
diff --git a/app/javascript/glitch/components/account/header.js b/app/javascript/glitch/components/account/header.js
index c94fb0851..7bc1a2189 100644
--- a/app/javascript/glitch/components/account/header.js
+++ b/app/javascript/glitch/components/account/header.js
@@ -51,6 +51,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import emojify from '../../../mastodon/features/emoji/emoji';
 import IconButton from '../../../mastodon/components/icon_button';
 import Avatar from '../../../mastodon/components/avatar';
+import { me } from '../../../mastodon/initial_state';
 
 //  Our imports  //
 import { processBio } from '../../util/bio_metadata';
@@ -88,7 +89,6 @@ export default class AccountHeader extends ImmutablePureComponent {
 
   static propTypes = {
     account  : ImmutablePropTypes.map,
-    me       : PropTypes.string.isRequired,
     onFollow : PropTypes.func.isRequired,
     intl     : PropTypes.object.isRequired,
   };
@@ -102,7 +102,7 @@ The `render()` function is used to render our component.
 */
 
   render () {
-    const { account, me, intl } = this.props;
+    const { account, intl } = this.props;
 
 /*
 
diff --git a/app/javascript/glitch/components/status/action_bar.js b/app/javascript/glitch/components/status/action_bar.js
index f4450d31b..34588b008 100644
--- a/app/javascript/glitch/components/status/action_bar.js
+++ b/app/javascript/glitch/components/status/action_bar.js
@@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import RelativeTimestamp from '../../../mastodon/components/relative_timestamp';
 import IconButton from '../../../mastodon/components/icon_button';
 import DropdownMenuContainer from '../../../mastodon/containers/dropdown_menu_container';
+import { me } from '../../../mastodon/initial_state';
 
 const messages = defineMessages({
   delete: { id: 'status.delete', defaultMessage: 'Delete' },
@@ -50,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
     onEmbed: PropTypes.func,
     onMuteConversation: PropTypes.func,
     onPin: PropTypes.func,
-    me: PropTypes.string,
     withDismiss: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
@@ -59,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
   // evaluate to false. See react-immutable-pure-component for usage.
   updateOnProps = [
     'status',
-    'me',
     'withDismiss',
   ]
 
@@ -119,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
   }
 
   render () {
-    const { status, me, intl, withDismiss } = this.props;
+    const { status, intl, withDismiss } = this.props;
 
     const mutingConversation = status.get('muted');
     const anonymousAccess = !me;
diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js
index 24261e763..0054abd14 100644
--- a/app/javascript/glitch/components/status/container.js
+++ b/app/javascript/glitch/components/status/container.js
@@ -140,12 +140,10 @@ Here are the props we pass to `<Status>`.
     return {
       status      : status,
       account     : account || ownProps.account,
-      me          : state.getIn(['meta', 'me']),
       settings    : state.get('local_settings'),
       prepend     : prepend || ownProps.prepend,
       reblogModal : state.getIn(['meta', 'boost_modal']),
       deleteModal : state.getIn(['meta', 'delete_modal']),
-      autoPlayGif : state.getIn(['meta', 'auto_play_gif']),
     };
   };
 
diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js
index 6bd95b051..33a9730e5 100644
--- a/app/javascript/glitch/components/status/index.js
+++ b/app/javascript/glitch/components/status/index.js
@@ -39,6 +39,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 
 //  Mastodon imports  //
 import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task';
+import { autoPlayGif } from '../../../mastodon/initial_state';
 
 //  Our imports  //
 import StatusPrepend from './prepend';
@@ -89,9 +90,6 @@ few parts:
     These are our local settings, fetched from our store. We need this
     to determine how best to collapse our statuses, among other things.
 
- -  __`me` (`PropTypes.number`) :__
-    This is the id of the currently-signed-in user.
-
  -  __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`,
     `onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`,
     `onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__
@@ -103,9 +101,6 @@ few parts:
     reblogging and deleting statuses. They are used by the `onReblog`
     and `onDelete` functions, but we don't deal with them here.
 
- -  __`autoPlayGif` (`PropTypes.bool`) :__
-    This tells the frontend whether or not to autoplay gifs!
-
  -  __`muted` (`PropTypes.bool`) :__
     This has nothing to do with a user or conversation mute! "Muted" is
     what Mastodon internally calls the subdued look of statuses in the
@@ -160,7 +155,6 @@ export default class Status extends ImmutablePureComponent {
     account                     : ImmutablePropTypes.map,
     settings                    : ImmutablePropTypes.map,
     notification                : ImmutablePropTypes.map,
-    me                          : PropTypes.string,
     onFavourite                 : PropTypes.func,
     onReblog                    : PropTypes.func,
     onModalReblog               : PropTypes.func,
@@ -177,7 +171,6 @@ export default class Status extends ImmutablePureComponent {
     onOpenVideo                 : PropTypes.func,
     reblogModal                 : PropTypes.bool,
     deleteModal                 : PropTypes.bool,
-    autoPlayGif                 : PropTypes.bool,
     muted                       : PropTypes.bool,
     collapse                    : PropTypes.bool,
     prepend                     : PropTypes.string,
@@ -211,9 +204,7 @@ to remember to specify it here.
     'account',
     'settings',
     'prepend',
-    'me',
     'boostModal',
-    'autoPlayGif',
     'muted',
     'collapse',
     'notification',
@@ -560,7 +551,6 @@ this operation are further explained in the code below.
       intersectionObserverWrapper,
       onOpenVideo,
       onOpenMedia,
-      autoPlayGif,
       notification,
       ...other
     } = this.props;
diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js
index 01bf8930b..3f40f6c2d 100644
--- a/app/javascript/mastodon/actions/pin_statuses.js
+++ b/app/javascript/mastodon/actions/pin_statuses.js
@@ -4,12 +4,13 @@ export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
 export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
 export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
 
+import { me } from '../initial_state';
+
 export function fetchPinnedStatuses() {
   return (dispatch, getState) => {
     dispatch(fetchPinnedStatusesRequest());
 
-    const accountId = getState().getIn(['meta', 'me']);
-    api(getState).get(`/api/v1/accounts/${accountId}/statuses`, { params: { pinned: true } }).then(response => {
+    api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => {
       dispatch(fetchPinnedStatusesSuccess(response.data, null));
     }).catch(error => {
       dispatch(fetchPinnedStatusesFail(error));
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index a2e25c930..e60ddacd9 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -1,4 +1,4 @@
-import createStream from '../stream';
+import { connectStream } from '../stream';
 import {
   updateTimeline,
   deleteFromTimelines,
@@ -12,42 +12,19 @@ import { getLocale } from '../locales';
 const { messages } = getLocale();
 
 export function connectTimelineStream (timelineId, path, pollingRefresh = null) {
-  return (dispatch, getState) => {
-    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
-    const accessToken = getState().getIn(['meta', 'access_token']);
-    const locale = getState().getIn(['meta', 'locale']);
-    let polling = null;
-
-    const setupPolling = () => {
-      polling = setInterval(() => {
-        pollingRefresh(dispatch);
-      }, 20000);
-    };
-
-    const clearPolling = () => {
-      if (polling) {
-        clearInterval(polling);
-        polling = null;
-      }
-    };
-
-    const subscription = createStream(streamingAPIBaseURL, accessToken, path, {
 
-      connected () {
-        if (pollingRefresh) {
-          clearPolling();
-        }
+  return connectStream (path, pollingRefresh, (dispatch, getState) => {
+    const locale = getState().getIn(['meta', 'locale']);
+    return {
+      onConnect() {
         dispatch(connectTimeline(timelineId));
       },
 
-      disconnected () {
-        if (pollingRefresh) {
-          setupPolling();
-        }
+      onDisconnect() {
         dispatch(disconnectTimeline(timelineId));
       },
 
-      received (data) {
+      onReceive (data) {
         switch(data.event) {
         case 'update':
           dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
@@ -60,26 +37,8 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
           break;
         }
       },
-
-      reconnected () {
-        if (pollingRefresh) {
-          clearPolling();
-          pollingRefresh(dispatch);
-        }
-        dispatch(connectTimeline(timelineId));
-      },
-
-    });
-
-    const disconnect = () => {
-      if (subscription) {
-        subscription.close();
-      }
-      clearPolling();
     };
-
-    return disconnect;
-  };
+  });
 }
 
 function refreshHomeTimelineAndNotification (dispatch) {
diff --git a/app/javascript/mastodon/components/account.js b/app/javascript/mastodon/components/account.js
index 376e544fb..2c3a00064 100644
--- a/app/javascript/mastodon/components/account.js
+++ b/app/javascript/mastodon/components/account.js
@@ -7,6 +7,7 @@ import Permalink from './permalink';
 import IconButton from './icon_button';
 import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import { me } from '../initial_state';
 
 const messages = defineMessages({
   follow: { id: 'account.follow', defaultMessage: 'Follow' },
@@ -23,7 +24,6 @@ export default class Account extends ImmutablePureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
-    me: PropTypes.string.isRequired,
     onFollow: PropTypes.func.isRequired,
     onBlock: PropTypes.func.isRequired,
     onMute: PropTypes.func.isRequired,
@@ -52,7 +52,7 @@ export default class Account extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, me, intl, hidden } = this.props;
+    const { account, intl, hidden } = this.props;
 
     if (!account) {
       return <div />;
@@ -82,7 +82,7 @@ export default class Account extends ImmutablePureComponent {
       } else if (muting) {
         let hidingNotificationsButton;
         if (muting.get('notifications')) {
-          hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username')  })} onClick={this.handleUnmuteNotifications} />;
+          hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />;
         } else {
           hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username')  })} onClick={this.handleMuteNotifications} />;
         }
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index 76b0da12f..d0c1b049f 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -65,6 +65,7 @@ export default class IconButton extends React.PureComponent {
       expanded,
       icon,
       inverted,
+      flip,
       overlay,
       pressed,
       tabIndex,
@@ -78,8 +79,8 @@ export default class IconButton extends React.PureComponent {
       overlayed: overlay,
     });
 
-    const flipDeg = this.props.flip ? -180 : -360;
-    const rotateDeg = this.props.active ? flipDeg : 0;
+    const flipDeg = flip ? -180 : -360;
+    const rotateDeg = active ? flipDeg : 0;
 
     const motionDefaultStyle = {
       rotate: rotateDeg,
@@ -93,6 +94,25 @@ export default class IconButton extends React.PureComponent {
       rotate: animate ? spring(rotateDeg, springOpts) : 0,
     };
 
+    if (!animate) {
+      // Perf optimization: avoid unnecessary <Motion> components unless
+      // we actually need to animate.
+      return (
+        <button
+          aria-label={title}
+          aria-pressed={pressed}
+          aria-expanded={expanded}
+          title={title}
+          className={classes}
+          onClick={this.handleClick}
+          style={style}
+          tabIndex={tabIndex}
+        >
+          <i className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
+        </button>
+      );
+    }
+
     return (
       <Motion defaultStyle={motionDefaultStyle} style={motionStyle}>
         {({ rotate }) =>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 83cf8b871..5ed46dc93 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -9,6 +9,7 @@ import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
+import { autoPlayGif } from '../initial_state';
 
 const messages = defineMessages({
   toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
@@ -26,11 +27,9 @@ class Item extends React.PureComponent {
     index: PropTypes.number.isRequired,
     size: PropTypes.number.isRequired,
     onClick: PropTypes.func.isRequired,
-    autoPlayGif: PropTypes.bool,
   };
 
   static defaultProps = {
-    autoPlayGif: false,
     standalone: false,
     index: 0,
     size: 1,
@@ -50,7 +49,7 @@ class Item extends React.PureComponent {
   }
 
   hoverToPlay () {
-    const { attachment, autoPlayGif } = this.props;
+    const { attachment } = this.props;
     return !autoPlayGif && attachment.get('type') === 'gifv';
   }
 
@@ -142,7 +141,7 @@ class Item extends React.PureComponent {
         </a>
       );
     } else if (attachment.get('type') === 'gifv') {
-      const autoPlay = !isIOS() && this.props.autoPlayGif;
+      const autoPlay = !isIOS() && autoPlayGif;
 
       thumbnail = (
         <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
@@ -184,11 +183,9 @@ export default class MediaGallery extends React.PureComponent {
     height: PropTypes.number.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-    autoPlayGif: PropTypes.bool,
   };
 
   static defaultProps = {
-    autoPlayGif: false,
     standalone: false,
   };
 
@@ -264,9 +261,9 @@ export default class MediaGallery extends React.PureComponent {
       const size = media.take(4).size;
 
       if (this.isStandaloneEligible()) {
-        children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />;
+        children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
       } else {
-        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />);
+        children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
       }
     }
 
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index ab9d48510..71228ca6c 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import PropTypes from 'prop-types';
 import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
 import LoadMore from './load_more';
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index b9be20033..5a01c0cdd 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -39,9 +39,6 @@ export default class Status extends ImmutablePureComponent {
     onBlock: PropTypes.func,
     onEmbed: PropTypes.func,
     onHeightChange: PropTypes.func,
-    me: PropTypes.string,
-    boostModal: PropTypes.bool,
-    autoPlayGif: PropTypes.bool,
     muted: PropTypes.bool,
     hidden: PropTypes.bool,
     onMoveUp: PropTypes.func,
@@ -57,9 +54,6 @@ export default class Status extends ImmutablePureComponent {
   updateOnProps = [
     'status',
     'account',
-    'me',
-    'boostModal',
-    'autoPlayGif',
     'muted',
     'hidden',
   ]
@@ -200,7 +194,7 @@ export default class Status extends ImmutablePureComponent {
       } else {
         media = (
           <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
-            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />}
+            {Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
           </Bundle>
         );
       }
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index af152cc32..35daf70b9 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -8,6 +8,7 @@ import IconButton from './icon_button';
 import DropdownMenuContainer from '../containers/dropdown_menu_container';
 import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import { me } from '../initial_state';
 
 const messages = defineMessages({
   delete: { id: 'status.delete', defaultMessage: 'Delete' },
@@ -50,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
     onEmbed: PropTypes.func,
     onMuteConversation: PropTypes.func,
     onPin: PropTypes.func,
-    me: PropTypes.string,
     withDismiss: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
@@ -59,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
   // evaluate to false. See react-immutable-pure-component for usage.
   updateOnProps = [
     'status',
-    'me',
     'withDismiss',
   ]
 
@@ -119,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
   }
 
   render () {
-    const { status, me, intl, withDismiss } = this.props;
+    const { status, intl, withDismiss } = this.props;
 
     const mutingConversation = status.get('muted');
     const anonymousAccess    = !me;
diff --git a/app/javascript/mastodon/containers/account_container.js b/app/javascript/mastodon/containers/account_container.js
index 5728c878e..5a5136dd1 100644
--- a/app/javascript/mastodon/containers/account_container.js
+++ b/app/javascript/mastodon/containers/account_container.js
@@ -13,6 +13,7 @@ import {
 } from '../actions/accounts';
 import { openModal } from '../actions/modal';
 import { initMuteModal } from '../actions/mutes';
+import { unfollowModal } from '../initial_state';
 
 const messages = defineMessages({
   unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
@@ -23,8 +24,6 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     account: getAccount(state, props.id),
-    me: state.getIn(['meta', 'me']),
-    unfollowModal: state.getIn(['meta', 'unfollow_modal']),
   });
 
   return mapStateToProps;
@@ -34,7 +33,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onFollow (account) {
     if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
-      if (this.unfollowModal) {
+      if (unfollowModal) {
         dispatch(openModal('CONFIRM', {
           message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
           confirm: intl.formatMessage(messages.unfollowConfirm),
diff --git a/app/javascript/mastodon/containers/compose_container.js b/app/javascript/mastodon/containers/compose_container.js
index db452d03a..5ee1d2f14 100644
--- a/app/javascript/mastodon/containers/compose_container.js
+++ b/app/javascript/mastodon/containers/compose_container.js
@@ -6,15 +6,14 @@ import { hydrateStore } from '../actions/store';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import Compose from '../features/standalone/compose';
+import initialState from '../initial_state';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
 
 const store = configureStore();
-const initialStateContainer = document.getElementById('initial-state');
 
-if (initialStateContainer !== null) {
-  const initialState = JSON.parse(initialStateContainer.textContent);
+if (initialState) {
   store.dispatch(hydrateStore(initialState));
 }
 
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index a7138e62d..d1710445b 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -4,23 +4,18 @@ import PropTypes from 'prop-types';
 import configureStore from '../store/configureStore';
 import { showOnboardingOnce } from '../actions/onboarding';
 import { BrowserRouter, Route } from 'react-router-dom';
-import { ScrollContext } from 'react-router-scroll';
+import { ScrollContext } from 'react-router-scroll-4';
 import UI from '../features/ui';
 import { hydrateStore } from '../actions/store';
 import { connectUserStream } from '../actions/streaming';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
+import initialState from '../initial_state';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
 
 export const store = configureStore();
-const initialState = JSON.parse(document.getElementById('initial-state').textContent);
-try {
-  initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
-} catch (e) {
-  initialState.local_settings = {};
-}
 const hydrateAction = hydrateStore(initialState);
 store.dispatch(hydrateAction);
 
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index e8821223d..b9c461f31 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -17,20 +17,18 @@ import {
   pin,
   unpin,
 } from '../actions/interactions';
-import {
-  blockAccount,
-  muteAccount,
-} from '../actions/accounts';
+import { blockAccount } from '../actions/accounts';
 import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
+import { initMuteModal } from '../actions/mutes';
 import { initReport } from '../actions/reports';
 import { openModal } from '../actions/modal';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { boostModal, deleteModal } from '../initial_state';
 
 const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
   deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
   blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
-  muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
 });
 
 const makeMapStateToProps = () => {
@@ -38,10 +36,6 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     status: getStatus(state, props.id),
-    me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal']),
-    deleteModal: state.getIn(['meta', 'delete_modal']),
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
   });
 
   return mapStateToProps;
@@ -61,7 +55,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     if (status.get('reblogged')) {
       dispatch(unreblog(status));
     } else {
-      if (e.shiftKey || !this.boostModal) {
+      if (e.shiftKey || !boostModal) {
         this.onModalReblog(status);
       } else {
         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
@@ -90,7 +84,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 
   onDelete (status) {
-    if (!this.deleteModal) {
+    if (!deleteModal) {
       dispatch(deleteStatus(status.get('id')));
     } else {
       dispatch(openModal('CONFIRM', {
@@ -126,11 +120,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
   },
 
   onMute (account) {
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.muteConfirm),
-      onConfirm: () => dispatch(muteAccount(account.get('id'))),
-    }));
+    dispatch(initMuteModal(account));
   },
 
   onMuteConversation (status) {
diff --git a/app/javascript/mastodon/containers/timeline_container.js b/app/javascript/mastodon/containers/timeline_container.js
index 4be037955..e84c921ee 100644
--- a/app/javascript/mastodon/containers/timeline_container.js
+++ b/app/javascript/mastodon/containers/timeline_container.js
@@ -7,15 +7,14 @@ import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import PublicTimeline from '../features/standalone/public_timeline';
 import HashtagTimeline from '../features/standalone/hashtag_timeline';
+import initialState from '../initial_state';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
 
 const store = configureStore();
-const initialStateContainer = document.getElementById('initial-state');
 
-if (initialStateContainer !== null) {
-  const initialState = JSON.parse(initialStateContainer.textContent);
+if (initialState) {
   store.dispatch(hydrateStore(initialState));
 }
 
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
index 718e7fbad..389296c42 100644
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ b/app/javascript/mastodon/features/account/components/action_bar.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
 import { Link } from 'react-router-dom';
 import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
+import { me } from '../../../initial_state';
 
 const messages = defineMessages({
   mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
@@ -28,7 +29,6 @@ export default class ActionBar extends React.PureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
-    me: PropTypes.string.isRequired,
     onFollow: PropTypes.func,
     onBlock: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
@@ -47,7 +47,7 @@ export default class ActionBar extends React.PureComponent {
   }
 
   render () {
-    const { account, me, intl } = this.props;
+    const { account, intl } = this.props;
 
     let menu = [];
     let extraInfo = '';
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 57678d162..b3a73a590 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -8,8 +8,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import IconButton from '../../../components/icon_button';
 import Motion from '../../ui/util/optional_motion';
 import spring from 'react-motion/lib/spring';
-import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import { autoPlayGif, me } from '../../../initial_state';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -17,19 +17,10 @@ const messages = defineMessages({
   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
 });
 
-const makeMapStateToProps = () => {
-  const mapStateToProps = state => ({
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
-  });
-
-  return mapStateToProps;
-};
-
 class Avatar extends ImmutablePureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map.isRequired,
-    autoPlayGif: PropTypes.bool.isRequired,
   };
 
   state = {
@@ -47,7 +38,7 @@ class Avatar extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, autoPlayGif }   = this.props;
+    const { account }   = this.props;
     const { isHovered } = this.state;
 
     return (
@@ -74,20 +65,17 @@ class Avatar extends ImmutablePureComponent {
 
 }
 
-@connect(makeMapStateToProps)
 @injectIntl
 export default class Header extends ImmutablePureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map,
-    me: PropTypes.string.isRequired,
     onFollow: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-    autoPlayGif: PropTypes.bool.isRequired,
   };
 
   render () {
-    const { account, me, intl } = this.props;
+    const { account, intl } = this.props;
 
     if (!account) {
       return null;
@@ -127,7 +115,7 @@ export default class Header extends ImmutablePureComponent {
     return (
       <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
         <div>
-          <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
+          <Avatar account={account} />
 
           <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
           <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index 2a88addc4..a40722417 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -12,14 +12,13 @@ import { getAccountGallery } from '../../selectors';
 import MediaItem from './components/media_item';
 import HeaderContainer from '../account_timeline/containers/header_container';
 import { FormattedMessage } from 'react-intl';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import LoadMore from '../../components/load_more';
 
 const mapStateToProps = (state, props) => ({
   medias: getAccountGallery(state, props.params.accountId),
   isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
   hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']),
-  autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
 });
 
 @connect(mapStateToProps)
@@ -31,7 +30,6 @@ export default class AccountGallery extends ImmutablePureComponent {
     medias: ImmutablePropTypes.list.isRequired,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
-    autoPlayGif: PropTypes.bool,
   };
 
   componentDidMount () {
@@ -67,7 +65,7 @@ export default class AccountGallery extends ImmutablePureComponent {
   }
 
   render () {
-    const { medias, autoPlayGif, isLoading, hasMore } = this.props;
+    const { medias, isLoading, hasMore } = this.props;
 
     let loadMore = null;
 
@@ -100,7 +98,6 @@ export default class AccountGallery extends ImmutablePureComponent {
                 <MediaItem
                   key={media.get('id')}
                   media={media}
-                  autoPlayGif={autoPlayGif}
                 />
               )}
               {loadMore}
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index b33df282f..9a087e922 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -10,7 +10,6 @@ export default class Header extends ImmutablePureComponent {
 
   static propTypes = {
     account: ImmutablePropTypes.map,
-    me: PropTypes.string.isRequired,
     onFollow: PropTypes.func.isRequired,
     onBlock: PropTypes.func.isRequired,
     onMention: PropTypes.func.isRequired,
@@ -66,7 +65,7 @@ export default class Header extends ImmutablePureComponent {
   }
 
   render () {
-    const { account, me } = this.props;
+    const { account } = this.props;
 
     if (account === null) {
       return <MissingIndicator />;
@@ -76,13 +75,11 @@ export default class Header extends ImmutablePureComponent {
       <div className='account-timeline__header'>
         <InnerHeader
           account={account}
-          me={me}
           onFollow={this.handleFollow}
         />
 
         <ActionBar
           account={account}
-          me={me}
           onBlock={this.handleBlock}
           onMention={this.handleMention}
           onReblogToggle={this.handleReblogToggle}
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 68c037e9b..b41eb19d4 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -15,6 +15,7 @@ import { initReport } from '../../../actions/reports';
 import { openModal } from '../../../actions/modal';
 import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { unfollowModal } from '../../../initial_state';
 
 const messages = defineMessages({
   unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
@@ -27,8 +28,6 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, { accountId }) => ({
     account: getAccount(state, accountId),
-    me: state.getIn(['meta', 'me']),
-    unfollowModal: state.getIn(['meta', 'unfollow_modal']),
   });
 
   return mapStateToProps;
@@ -38,7 +37,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onFollow (account) {
     if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
-      if (this.unfollowModal) {
+      if (unfollowModal) {
         dispatch(openModal('CONFIRM', {
           message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
           confirm: intl.formatMessage(messages.unfollowConfirm),
diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js
index e3b864aee..3ad370e32 100644
--- a/app/javascript/mastodon/features/account_timeline/index.js
+++ b/app/javascript/mastodon/features/account_timeline/index.js
@@ -16,7 +16,6 @@ const mapStateToProps = (state, props) => ({
   statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
   isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
   hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
-  me: state.getIn(['meta', 'me']),
 });
 
 @connect(mapStateToProps)
@@ -28,7 +27,6 @@ export default class AccountTimeline extends ImmutablePureComponent {
     statusIds: ImmutablePropTypes.list,
     isLoading: PropTypes.bool,
     hasMore: PropTypes.bool,
-    me: PropTypes.string.isRequired,
   };
 
   componentWillMount () {
@@ -50,7 +48,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, isLoading, hasMore, me } = this.props;
+    const { statusIds, isLoading, hasMore } = this.props;
 
     if (!statusIds && isLoading) {
       return (
@@ -70,7 +68,6 @@ export default class AccountTimeline extends ImmutablePureComponent {
           statusIds={statusIds}
           isLoading={isLoading}
           hasMore={hasMore}
-          me={me}
           onScrollToBottom={this.handleScrollToBottom}
         />
       </Column>
diff --git a/app/javascript/mastodon/features/blocks/index.js b/app/javascript/mastodon/features/blocks/index.js
index e73d984a9..9199529dd 100644
--- a/app/javascript/mastodon/features/blocks/index.js
+++ b/app/javascript/mastodon/features/blocks/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountContainer from '../../containers/account_container';
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 2da656fc0..aaca45493 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -19,6 +19,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import { length } from 'stringz';
 import { countableText } from '../util/counter';
 import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index';
+import initialState from '../../../initial_state';
+
+const maxChars = initialState.max_toot_chars;
 
 const messages = defineMessages({
   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
@@ -45,7 +48,6 @@ export default class ComposeForm extends ImmutablePureComponent {
     preselectDate: PropTypes.instanceOf(Date),
     is_submitting: PropTypes.bool,
     is_uploading: PropTypes.bool,
-    me: PropTypes.string,
     onChange: PropTypes.func.isRequired,
     onSubmit: PropTypes.func.isRequired,
     onClearSuggestions: PropTypes.func.isRequired,
@@ -206,7 +208,7 @@ export default class ComposeForm extends ImmutablePureComponent {
       }
     }
 
-    const submitDisabled = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0);
+    const submitDisabled = disabled || this.props.is_uploading || length(text) > maxChars || (text.length !== 0 && text.trim().length === 0);
 
     return (
       <div className='compose-form'>
@@ -256,7 +258,7 @@ export default class ComposeForm extends ImmutablePureComponent {
         </div>
 
         <div className='compose-form__publish'>
-          <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
+          <div className='character-counter__wrapper'><CharacterCounter max={maxChars} text={text} /></div>
           <div className='compose-form__publish-button-wrapper'>
             {
               showSideArm ?
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index dffa04ff0..dc8fc02ba 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -157,7 +157,6 @@ class EmojiPickerMenu extends React.PureComponent {
     intl: PropTypes.object.isRequired,
     skinTone: PropTypes.number.isRequired,
     onSkinTone: PropTypes.func.isRequired,
-    autoPlay: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -235,7 +234,7 @@ class EmojiPickerMenu extends React.PureComponent {
   }
 
   render () {
-    const { loading, style, intl, custom_emojis, autoPlay, skinTone, frequentlyUsedEmojis } = this.props;
+    const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
 
     if (loading) {
       return <div style={{ width: 299 }} />;
@@ -250,7 +249,7 @@ class EmojiPickerMenu extends React.PureComponent {
           perLine={8}
           emojiSize={22}
           sheetSize={32}
-          custom={buildCustomEmojis(custom_emojis, autoPlay)}
+          custom={buildCustomEmojis(custom_emojis)}
           color=''
           emoji=''
           set='twitter'
@@ -284,7 +283,6 @@ export default class EmojiPickerDropdown extends React.PureComponent {
   static propTypes = {
     custom_emojis: ImmutablePropTypes.list,
     frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
-    autoPlay: PropTypes.bool,
     intl: PropTypes.object.isRequired,
     onPickEmoji: PropTypes.func.isRequired,
     onSkinTone: PropTypes.func.isRequired,
@@ -346,7 +344,7 @@ export default class EmojiPickerDropdown extends React.PureComponent {
   }
 
   render () {
-    const { intl, onPickEmoji, autoPlay, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
+    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
     const title = intl.formatMessage(messages.emoji);
     const { active, loading } = this.state;
 
@@ -366,7 +364,6 @@ export default class EmojiPickerDropdown extends React.PureComponent {
             loading={loading}
             onClose={this.onHideDropdown}
             onPick={onPickEmoji}
-            autoPlay={autoPlay}
             onSkinTone={onSkinTone}
             skinTone={skinTone}
             frequentlyUsedEmojis={frequentlyUsedEmojis}
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index ffa0a3442..dfe8241c6 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -23,7 +23,6 @@ const mapStateToProps = state => ({
   preselectDate: state.getIn(['compose', 'preselectDate']),
   is_submitting: state.getIn(['compose', 'is_submitting']),
   is_uploading: state.getIn(['compose', 'is_uploading']),
-  me: state.getIn(['compose', 'me']),
   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
   settings: state.get('local_settings'),
   filesAttached: state.getIn(['compose', 'media_attachments']).size > 0,
diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
index 699687c69..e6a535a5d 100644
--- a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
+++ b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js
@@ -61,7 +61,6 @@ const getCustomEmojis = createSelector([
 
 const mapStateToProps = state => ({
   custom_emojis: getCustomEmojis(state),
-  autoPlay: state.getIn(['meta', 'auto_play_gif']),
   skinTone: state.getIn(['settings', 'skinTone']),
   frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
 });
diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js
index 8cc53c087..eb9f3ea45 100644
--- a/app/javascript/mastodon/features/compose/containers/navigation_container.js
+++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js
@@ -1,9 +1,10 @@
 import { connect }   from 'react-redux';
 import NavigationBar from '../components/navigation_bar';
+import { me } from '../../../initial_state';
 
 const mapStateToProps = state => {
   return {
-    account: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
+    account: state.getIn(['accounts', me]),
   };
 };
 
diff --git a/app/javascript/mastodon/features/compose/containers/warning_container.js b/app/javascript/mastodon/features/compose/containers/warning_container.js
index 35eab5976..d34471a3e 100644
--- a/app/javascript/mastodon/features/compose/containers/warning_container.js
+++ b/app/javascript/mastodon/features/compose/containers/warning_container.js
@@ -3,9 +3,10 @@ import { connect } from 'react-redux';
 import Warning from '../components/warning';
 import PropTypes from 'prop-types';
 import { FormattedMessage } from 'react-intl';
+import { me } from '../../../initial_state';
 
 const mapStateToProps = state => ({
-  needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked']),
+  needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
 });
 
 const WarningWrapper = ({ needsLockWarning }) => {
diff --git a/app/javascript/mastodon/features/compose/util/counter.js b/app/javascript/mastodon/features/compose/util/counter.js
index e6d2487c5..700ba2163 100644
--- a/app/javascript/mastodon/features/compose/util/counter.js
+++ b/app/javascript/mastodon/features/compose/util/counter.js
@@ -5,5 +5,5 @@ const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx';
 export function countableText(inputText) {
   return inputText
     .replace(urlRegex, urlPlaceholder)
-    .replace(/(?:^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '@$2');
+    .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3');
 };
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
index 636402172..372459c78 100644
--- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
+++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
@@ -57,5 +57,21 @@ describe('emoji', () => {
     it('does an emoji whose filename is irregular', () => {
       expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg" />');
     });
+
+    it('avoid emojifying on invisible text', () => {
+      expect(emojify('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>'))
+        .toEqual('<a href="http://example.com/test%F0%9F%98%84"><span class="invisible">http://</span><span class="ellipsis">example.com/te</span><span class="invisible">st😄</span></a>');
+      expect(emojify('<span class="invisible">:luigi:</span>', { ':luigi:': { static_url: 'luigi.exe' } }))
+        .toEqual('<span class="invisible">:luigi:</span>');
+    });
+
+    it('avoid emojifying on invisible text with nested tags', () => {
+      expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
+        .toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
+      expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
+        .toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
+      expect(emojify('<span class="invisible">😄<br/>😴</span>😇'))
+        .toEqual('<span class="invisible">😄<br/>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg" />');
+    });
   });
 });
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index b70fc2b37..0f005dd50 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -1,3 +1,4 @@
+import { autoPlayGif } from '../../initial_state';
 import unicodeMapping from './emoji_unicode_mapping_light';
 import Trie from 'substring-trie';
 
@@ -5,13 +6,13 @@ const trie = new Trie(Object.keys(unicodeMapping));
 
 const assetHost = process.env.CDN_HOST || '';
 
-let allowAnimations = false;
-
 const emojify = (str, customEmojis = {}) => {
-  let rtn = '';
+  const tagCharsWithoutEmojis = '<&';
+  const tagCharsWithEmojis = Object.keys(customEmojis).length ? '<&:' : '<&';
+  let rtn = '', tagChars = tagCharsWithEmojis, invisible = 0;
   for (;;) {
     let match, i = 0, tag;
-    while (i < str.length && (tag = '<&:'.indexOf(str[i])) === -1 && !(match = trie.search(str.slice(i)))) {
+    while (i < str.length && (tag = tagChars.indexOf(str[i])) === -1 && (invisible || !(match = trie.search(str.slice(i))))) {
       i += str.codePointAt(i) < 65536 ? 1 : 2;
     }
     let rend, replacement = '';
@@ -27,7 +28,7 @@ const emojify = (str, customEmojis = {}) => {
         // now got a replacee as ':shortname:'
         // if you want additional emoji handler, add statements below which set replacement and return true.
         if (shortname in customEmojis) {
-          const filename = allowAnimations ? customEmojis[shortname].url : customEmojis[shortname].static_url;
+          const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
           replacement = `<img draggable="false" class="emojione" alt="${shortname}" title="${shortname}" src="${filename}" />`;
           return true;
         }
@@ -35,7 +36,26 @@ const emojify = (str, customEmojis = {}) => {
       })()) rend = ++i;
     } else if (tag >= 0) { // <, &
       rend = str.indexOf('>;'[tag], i + 1) + 1;
-      if (!rend) break;
+      if (!rend) {
+        break;
+      }
+      if (tag === 0) {
+        if (invisible) {
+          if (str[i + 1] === '/') { // closing tag
+            if (!--invisible) {
+              tagChars = tagCharsWithEmojis;
+            }
+          } else if (str[rend - 2] !== '/') { // opening tag
+            invisible++;
+          }
+        } else {
+          if (str.startsWith('<span class="invisible">', i)) {
+            // avoid emojifying on invisible text
+            invisible = 1;
+            tagChars = tagCharsWithoutEmojis;
+          }
+        }
+      }
       i = rend;
     } else { // matched to unicode emoji
       const { filename, shortCode } = unicodeMapping[match];
@@ -51,14 +71,12 @@ const emojify = (str, customEmojis = {}) => {
 
 export default emojify;
 
-export const buildCustomEmojis = (customEmojis, overrideAllowAnimations = false) => {
+export const buildCustomEmojis = (customEmojis) => {
   const emojis = [];
 
-  allowAnimations = overrideAllowAnimations;
-
   customEmojis.forEach(emoji => {
     const shortcode = emoji.get('shortcode');
-    const url       = allowAnimations ? emoji.get('url') : emoji.get('static_url');
+    const url       = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
     const name      = shortcode.replace(':', '');
 
     emojis.push({
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js
index c0cba952a..e5b834a74 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.js
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js
@@ -64,14 +64,14 @@ Object.keys(emojiMap).forEach(key => {
 
 Object.keys(emojiIndex.emojis).forEach(key => {
   const { native } = emojiIndex.emojis[key];
-  const { short_names, search, unified } = emojiMartData.emojis[key];
+  let { short_names, search, unified } = emojiMartData.emojis[key];
   if (short_names[0] !== key) {
     throw new Error('The compresser expects the first short_code to be the ' +
       'key. It may need to be rewritten if the emoji change such that this ' +
       'is no longer the case.');
   }
 
-  short_names.splice(0, 1); // first short name can be inferred from the key
+  short_names = short_names.slice(1); // first short name can be inferred from the key
 
   const searchData = [native, short_names, search];
   if (unicodeToUnifiedName(native) !== unified) {
diff --git a/app/javascript/mastodon/features/favourites/index.js b/app/javascript/mastodon/features/favourites/index.js
index 4dbfefd87..6f113beb4 100644
--- a/app/javascript/mastodon/features/favourites/index.js
+++ b/app/javascript/mastodon/features/favourites/index.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchFavourites } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import ColumnBackButton from '../../components/column_back_button';
diff --git a/app/javascript/mastodon/features/follow_requests/index.js b/app/javascript/mastodon/features/follow_requests/index.js
index 94109b151..1fa52d511 100644
--- a/app/javascript/mastodon/features/follow_requests/index.js
+++ b/app/javascript/mastodon/features/follow_requests/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountAuthorizeContainer from './containers/account_authorize_container';
diff --git a/app/javascript/mastodon/features/followers/index.js b/app/javascript/mastodon/features/followers/index.js
index 89445559f..f64ed7948 100644
--- a/app/javascript/mastodon/features/followers/index.js
+++ b/app/javascript/mastodon/features/followers/index.js
@@ -8,7 +8,7 @@ import {
   fetchFollowers,
   expandFollowers,
 } from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import HeaderContainer from '../account_timeline/containers/header_container';
diff --git a/app/javascript/mastodon/features/following/index.js b/app/javascript/mastodon/features/following/index.js
index c34830276..a0c0fac05 100644
--- a/app/javascript/mastodon/features/following/index.js
+++ b/app/javascript/mastodon/features/following/index.js
@@ -8,7 +8,7 @@ import {
   fetchFollowing,
   expandFollowing,
 } from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import HeaderContainer from '../account_timeline/containers/header_container';
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index 9b94b9830..2f7d9281e 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -8,6 +8,7 @@ import { openModal } from '../../actions/modal';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import { me } from '../../initial_state';
 
 const messages = defineMessages({
   heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -31,7 +32,7 @@ const messages = defineMessages({
 });
 
 const mapStateToProps = state => ({
-  me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
+  myAccount: state.getIn(['accounts', me]),
   columns: state.getIn(['settings', 'columns']),
 });
 
@@ -41,7 +42,7 @@ export default class GettingStarted extends ImmutablePureComponent {
 
   static propTypes = {
     intl: PropTypes.object.isRequired,
-    me: ImmutablePropTypes.map.isRequired,
+    myAccount: ImmutablePropTypes.map.isRequired,
     columns: ImmutablePropTypes.list,
     multiColumn: PropTypes.bool,
     dispatch: PropTypes.func.isRequired,
@@ -57,7 +58,7 @@ export default class GettingStarted extends ImmutablePureComponent {
   }
 
   render () {
-    const { intl, me, columns, multiColumn } = this.props;
+    const { intl, myAccount, columns, multiColumn } = this.props;
 
     let navItems = [];
 
@@ -88,7 +89,7 @@ export default class GettingStarted extends ImmutablePureComponent {
       <ColumnLink key='6' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
     ]);
 
-    if (me.get('locked')) {
+    if (myAccount.get('locked')) {
       navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
     }
 
diff --git a/app/javascript/mastodon/features/mutes/index.js b/app/javascript/mastodon/features/mutes/index.js
index 0f3b8e710..ae6ec343f 100644
--- a/app/javascript/mastodon/features/mutes/index.js
+++ b/app/javascript/mastodon/features/mutes/index.js
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import Column from '../ui/components/column';
 import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import AccountContainer from '../../containers/account_container';
diff --git a/app/javascript/mastodon/features/reblogs/index.js b/app/javascript/mastodon/features/reblogs/index.js
index f1904786a..579d6aaa0 100644
--- a/app/javascript/mastodon/features/reblogs/index.js
+++ b/app/javascript/mastodon/features/reblogs/index.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import LoadingIndicator from '../../components/loading_indicator';
 import { fetchReblogs } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import AccountContainer from '../../containers/account_container';
 import Column from '../ui/components/column';
 import ColumnBackButton from '../../components/column_back_button';
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index 3e94f7446..8c6994a07 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -4,6 +4,7 @@ import IconButton from '../../../components/icon_button';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
 import { defineMessages, injectIntl } from 'react-intl';
+import { me } from '../../../initial_state';
 
 const messages = defineMessages({
   delete: { id: 'status.delete', defaultMessage: 'Delete' },
@@ -36,7 +37,6 @@ export default class ActionBar extends React.PureComponent {
     onReport: PropTypes.func,
     onPin: PropTypes.func,
     onEmbed: PropTypes.func,
-    me: PropTypes.string.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
@@ -80,7 +80,7 @@ export default class ActionBar extends React.PureComponent {
   }
 
   render () {
-    const { status, me, intl } = this.props;
+    const { status, intl } = this.props;
 
     const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
 
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index d8547db36..85a030ea8 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -25,7 +25,6 @@ export default class DetailedStatus extends ImmutablePureComponent {
     settings: ImmutablePropTypes.map.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
-    autoPlayGif: PropTypes.bool,
   };
 
   handleAccountClick = (e) => {
@@ -76,7 +75,6 @@ export default class DetailedStatus extends ImmutablePureComponent {
             fullwidth={settings.getIn(['media', 'fullwidth'])}
             height={250}
             onOpenMedia={this.props.onOpenMedia}
-            autoPlayGif={this.props.autoPlayGif}
           />
         );
         mediaIcon = 'picture-o';
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index c40630a0a..e7ea046dd 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { fetchStatus } from '../../actions/statuses';
 import MissingIndicator from '../../components/missing_indicator';
@@ -22,13 +23,15 @@ import {
 import { deleteStatus } from '../../actions/statuses';
 import { initReport } from '../../actions/reports';
 import { makeGetStatus } from '../../selectors';
-import { ScrollContainer } from 'react-router-scroll';
+import { ScrollContainer } from 'react-router-scroll-4';
 import ColumnBackButton from '../../components/column_back_button';
 import StatusContainer from '../../../glitch/components/status/container';
 import { openModal } from '../../actions/modal';
 import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { HotKeys } from 'react-hotkeys';
+import { boostModal, deleteModal } from '../../initial_state';
+import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
 
 const messages = defineMessages({
   deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@@ -43,10 +46,6 @@ const makeMapStateToProps = () => {
     settings: state.get('local_settings'),
     ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
     descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
-    me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal']),
-    deleteModal: state.getIn(['meta', 'delete_modal']),
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
   });
 
   return mapStateToProps;
@@ -67,17 +66,21 @@ export default class Status extends ImmutablePureComponent {
     settings: ImmutablePropTypes.map.isRequired,
     ancestorsIds: ImmutablePropTypes.list,
     descendantsIds: ImmutablePropTypes.list,
-    me: PropTypes.string,
-    boostModal: PropTypes.bool,
-    deleteModal: PropTypes.bool,
-    autoPlayGif: PropTypes.bool,
     intl: PropTypes.object.isRequired,
   };
 
+  state = {
+    fullscreen: false,
+  };
+
   componentWillMount () {
     this.props.dispatch(fetchStatus(this.props.params.statusId));
   }
 
+  componentDidMount () {
+    attachFullscreenListener(this.onFullScreenChange);
+  }
+
   componentWillReceiveProps (nextProps) {
     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
       this._scrolledIntoView = false;
@@ -113,7 +116,7 @@ export default class Status extends ImmutablePureComponent {
     if (status.get('reblogged')) {
       this.props.dispatch(unreblog(status));
     } else {
-      if (e.shiftKey || !this.props.boostModal) {
+      if (e.shiftKey || !boostModal) {
         this.handleModalReblog(status);
       } else {
         this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
@@ -124,7 +127,7 @@ export default class Status extends ImmutablePureComponent {
   handleDeleteClick = (status) => {
     const { dispatch, intl } = this.props;
 
-    if (!this.props.deleteModal) {
+    if (!deleteModal) {
       dispatch(deleteStatus(status.get('id')));
     } else {
       dispatch(openModal('CONFIRM', {
@@ -259,9 +262,18 @@ export default class Status extends ImmutablePureComponent {
     }
   }
 
+  componentWillUnmount () {
+    detachFullscreenListener(this.onFullScreenChange);
+  }
+
+  onFullScreenChange = () => {
+    this.setState({ fullscreen: isFullscreen() });
+  }
+
   render () {
     let ancestors, descendants;
-    const { status, settings, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
+    const { status, settings, ancestorsIds, descendantsIds } = this.props;
+    const { fullscreen } = this.state;
 
     if (status === null) {
       return (
@@ -295,7 +307,7 @@ export default class Status extends ImmutablePureComponent {
         <ColumnBackButton />
 
         <ScrollContainer scrollKey='thread'>
-          <div className='scrollable detailed-status__wrapper' ref={this.setRef}>
+          <div className={classNames('scrollable', 'detailed-status__wrapper', { fullscreen })} ref={this.setRef}>
             {ancestors}
 
             <HotKeys handlers={handlers}>
@@ -303,15 +315,12 @@ export default class Status extends ImmutablePureComponent {
                 <DetailedStatus
                   status={status}
                   settings={settings}
-                  autoPlayGif={autoPlayGif}
-                  me={me}
                   onOpenVideo={this.handleOpenVideo}
                   onOpenMedia={this.handleOpenMedia}
                 />
 
                 <ActionBar
                   status={status}
-                  me={me}
                   onReply={this.handleReplyClick}
                   onFavourite={this.handleFavouriteClick}
                   onReblog={this.handleReblogClick}
diff --git a/app/javascript/mastodon/features/ui/components/mute_modal.js b/app/javascript/mastodon/features/ui/components/mute_modal.js
index b5e83bb71..73e48cf09 100644
--- a/app/javascript/mastodon/features/ui/components/mute_modal.js
+++ b/app/javascript/mastodon/features/ui/components/mute_modal.js
@@ -2,6 +2,7 @@ import React from 'react';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import { injectIntl, FormattedMessage } from 'react-intl';
+import Toggle from 'react-toggle';
 import Button from '../../../components/button';
 import { closeModal } from '../../../actions/modal';
 import { muteAccount } from '../../../actions/accounts';
@@ -80,12 +81,13 @@ export default class MuteModal extends React.PureComponent {
               values={{ name: <strong>@{account.get('acct')}</strong> }}
             />
           </p>
-          <p>
+          <div>
             <label htmlFor='mute-modal__hide-notifications-checkbox'>
               <FormattedMessage id='mute_modal.hide_notifications' defaultMessage='Hide notifications from this user?' />
-              <input id='mute-modal__hide-notifications-checkbox' type='checkbox' checked={notifications} onChange={this.toggleNotifications} />
+              {' '}
+              <Toggle id='mute-modal__hide-notifications-checkbox' checked={notifications} onChange={this.toggleNotifications} />
             </label>
-          </p>
+          </div>
         </div>
 
         <div className='mute-modal__action-bar'>
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index daf6b485c..1f9f0cd03 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -14,6 +14,7 @@ import {
   List as ImmutableList,
   Map as ImmutableMap,
 } from 'immutable';
+import { me } from '../../../initial_state';
 
 const noop = () => { };
 
@@ -43,11 +44,11 @@ PageOne.propTypes = {
   domain: PropTypes.string.isRequired,
 };
 
-const PageTwo = ({ me }) => (
+const PageTwo = ({ myAccount }) => (
   <div className='onboarding-modal__page onboarding-modal__page-two'>
     <div className='figure non-interactive'>
       <div className='pseudo-drawer'>
-        <NavigationBar onClose={noop} account={me} />
+        <NavigationBar onClose={noop} account={myAccount} />
       </div>
       <ComposeForm
         text='Awoo! #introductions'
@@ -73,10 +74,10 @@ const PageTwo = ({ me }) => (
 );
 
 PageTwo.propTypes = {
-  me: ImmutablePropTypes.map.isRequired,
+  myAccount: ImmutablePropTypes.map.isRequired,
 };
 
-const PageThree = ({ me }) => (
+const PageThree = ({ myAccount }) => (
   <div className='onboarding-modal__page onboarding-modal__page-three'>
     <div className='figure non-interactive'>
       <Search
@@ -88,7 +89,7 @@ const PageThree = ({ me }) => (
       />
 
       <div className='pseudo-drawer'>
-        <NavigationBar onClose={noop} account={me} />
+        <NavigationBar onClose={noop} account={myAccount} />
       </div>
     </div>
 
@@ -98,7 +99,7 @@ const PageThree = ({ me }) => (
 );
 
 PageThree.propTypes = {
-  me: ImmutablePropTypes.map.isRequired,
+  myAccount: ImmutablePropTypes.map.isRequired,
 };
 
 const PageFour = ({ domain, intl }) => (
@@ -166,7 +167,7 @@ PageSix.propTypes = {
 };
 
 const mapStateToProps = state => ({
-  me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
+  myAccount: state.getIn(['accounts', me]),
   admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
   domain: state.getIn(['meta', 'domain']),
 });
@@ -178,7 +179,7 @@ export default class OnboardingModal extends React.PureComponent {
   static propTypes = {
     onClose: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
-    me: ImmutablePropTypes.map.isRequired,
+    myAccount: ImmutablePropTypes.map.isRequired,
     domain: PropTypes.string.isRequired,
     admin: ImmutablePropTypes.map,
   };
@@ -188,11 +189,11 @@ export default class OnboardingModal extends React.PureComponent {
   };
 
   componentWillMount() {
-    const { me, admin, domain, intl } = this.props;
+    const { myAccount, admin, domain, intl } = this.props;
     this.pages = [
-      <PageOne acct={me.get('acct')} domain={domain} />,
-      <PageTwo me={me} />,
-      <PageThree me={me} />,
+      <PageOne acct={myAccount.get('acct')} domain={domain} />,
+      <PageTwo myAccount={myAccount} />,
+      <PageThree myAccount={myAccount} />,
       <PageFour domain={domain} intl={intl} />,
       <PageSix admin={admin} domain={domain} />,
     ];
diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js
index ff29bfdd4..a0aec4403 100644
--- a/app/javascript/mastodon/features/ui/containers/status_list_container.js
+++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js
@@ -4,13 +4,13 @@ import { scrollTopTimeline } from '../../../actions/timelines';
 import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
 import { createSelector } from 'reselect';
 import { debounce } from 'lodash';
+import { me } from '../../../initial_state';
 
 const makeGetStatusIds = () => createSelector([
   (state, { type }) => state.getIn(['settings', type], ImmutableMap()),
   (state, { type }) => state.getIn(['timelines', type, 'items'], ImmutableList()),
   (state)           => state.get('statuses'),
-  (state)           => state.getIn(['meta', 'me']),
-], (columnSettings, statusIds, statuses, me) => {
+], (columnSettings, statusIds, statuses) => {
   const rawRegex = columnSettings.getIn(['regex', 'body'], '').trim();
   let regex      = null;
 
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 9f77ab5aa..69eb1bbf7 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -40,18 +40,23 @@ import {
   PinnedStatuses,
 } from './util/async-components';
 import { HotKeys } from 'react-hotkeys';
+import { me } from '../../initial_state';
+import { defineMessages, injectIntl } from 'react-intl';
 
 // Dummy import, to make sure that <Status /> ends up in the application bundle.
 // Without this it ends up in ~8 very commonly used bundles.
 import '../../../glitch/components/status';
 
+const messages = defineMessages({
+  beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
+});
+
 const mapStateToProps = state => ({
-  systemFontUi: state.getIn(['meta', 'system_font_ui']),
+  isComposing: state.getIn(['compose', 'is_composing']),
+  hasComposingText: state.getIn(['compose', 'text']) !== '',
   layout: state.getIn(['local_settings', 'layout']),
   isWide: state.getIn(['local_settings', 'stretch']),
   navbarUnder: state.getIn(['local_settings', 'navbar_under']),
-  me: state.getIn(['meta', 'me']),
-  isComposing: state.getIn(['compose', 'is_composing']),
 });
 
 const keyMap = {
@@ -82,6 +87,7 @@ const keyMap = {
 };
 
 @connect(mapStateToProps)
+@injectIntl
 @withRouter
 export default class UI extends React.Component {
 
@@ -97,8 +103,9 @@ export default class UI extends React.Component {
     systemFontUi: PropTypes.bool,
     navbarUnder: PropTypes.bool,
     isComposing: PropTypes.bool,
-    me: PropTypes.string,
+    hasComposingText: PropTypes.bool,
     location: PropTypes.object,
+    intl: PropTypes.object.isRequired,
   };
 
   state = {
@@ -106,6 +113,17 @@ export default class UI extends React.Component {
     draggingOver: false,
   };
 
+  handleBeforeUnload = (e) => {
+    const { intl, isComposing, hasComposingText } = this.props;
+
+    if (isComposing && hasComposingText) {
+      // Setting returnValue to any string causes confirmation dialog.
+      // Many browsers no longer display this text to users,
+      // but we set user-friendly message for other browsers, e.g. Edge.
+      e.returnValue = intl.formatMessage(messages.beforeUnload);
+    }
+  }
+
   handleResize = debounce(() => {
     // The cached heights are no longer accurate, invalidate
     this.props.dispatch(clearHeight());
@@ -180,6 +198,7 @@ export default class UI extends React.Component {
   }
 
   componentWillMount () {
+    window.addEventListener('beforeunload', this.handleBeforeUnload, false);
     window.addEventListener('resize', this.handleResize, { passive: true });
     document.addEventListener('dragenter', this.handleDragEnter, false);
     document.addEventListener('dragover', this.handleDragOver, false);
@@ -222,6 +241,7 @@ export default class UI extends React.Component {
   }
 
   componentWillUnmount () {
+    window.removeEventListener('beforeunload', this.handleBeforeUnload);
     window.removeEventListener('resize', this.handleResize);
     document.removeEventListener('dragenter', this.handleDragEnter);
     document.removeEventListener('dragover', this.handleDragOver);
@@ -321,7 +341,7 @@ export default class UI extends React.Component {
   }
 
   handleHotkeyGoToProfile = () => {
-    this.context.router.history.push(`/accounts/${this.props.me}`);
+    this.context.router.history.push(`/accounts/${me}`);
   }
 
   handleHotkeyGoToBlocked = () => {
diff --git a/app/javascript/mastodon/features/ui/util/optional_motion.js b/app/javascript/mastodon/features/ui/util/optional_motion.js
index af6368738..df3a8b54a 100644
--- a/app/javascript/mastodon/features/ui/util/optional_motion.js
+++ b/app/javascript/mastodon/features/ui/util/optional_motion.js
@@ -1,56 +1,5 @@
-// Like react-motion's Motion, but checks to see if the user prefers
-// reduced motion and uses a cross-fade in those cases.
-
-import React from 'react';
+import { reduceMotion } from '../../../initial_state';
+import ReducedMotion from './reduced_motion';
 import Motion from 'react-motion/lib/Motion';
-import PropTypes from 'prop-types';
-
-const stylesToKeep = ['opacity', 'backgroundOpacity'];
-
-let reduceMotion;
-
-const extractValue = (value) => {
-  // This is either an object with a "val" property or it's a number
-  return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
-};
-
-class OptionalMotion extends React.Component {
-
-  static propTypes = {
-    defaultStyle: PropTypes.object,
-    style: PropTypes.object,
-    children: PropTypes.func,
-  }
-
-  render() {
-
-    const { style, defaultStyle, children } = this.props;
-
-    if (typeof reduceMotion !== 'boolean') {
-      // This never changes without a page reload, so we can just grab it
-      // once from the body classes as opposed to using Redux's connect(),
-      // which would unnecessarily update every state change
-      reduceMotion = document.body.classList.contains('reduce-motion');
-    }
-    if (reduceMotion) {
-      Object.keys(style).forEach(key => {
-        if (stylesToKeep.includes(key)) {
-          return;
-        }
-        // If it's setting an x or height or scale or some other value, we need
-        // to preserve the end-state value without actually animating it
-        style[key] = defaultStyle[key] = extractValue(style[key]);
-      });
-    }
-
-    return (
-      <Motion style={style} defaultStyle={defaultStyle}>
-        {children}
-      </Motion>
-    );
-  }
-
-}
-
 
-export default OptionalMotion;
+export default reduceMotion ? ReducedMotion : Motion;
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.js b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
index 86b30d488..43007ddc3 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.js
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.js
@@ -7,11 +7,19 @@ import BundleColumnError from '../components/bundle_column_error';
 import BundleContainer from '../containers/bundle_container';
 
 // Small wrapper to pass multiColumn to the route components
-export const WrappedSwitch = ({ multiColumn, children }) => (
-  <Switch>
-    {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
-  </Switch>
-);
+export class WrappedSwitch extends React.PureComponent {
+
+  render () {
+    const { multiColumn, children } = this.props;
+
+    return (
+      <Switch>
+        {React.Children.map(children, child => React.cloneElement(child, { multiColumn }))}
+      </Switch>
+    );
+  }
+
+}
 
 WrappedSwitch.propTypes = {
   multiColumn: PropTypes.bool,
diff --git a/app/javascript/mastodon/features/ui/util/reduced_motion.js b/app/javascript/mastodon/features/ui/util/reduced_motion.js
new file mode 100644
index 000000000..95519042b
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/util/reduced_motion.js
@@ -0,0 +1,44 @@
+// Like react-motion's Motion, but reduces all animations to cross-fades
+// for the benefit of users with motion sickness.
+import React from 'react';
+import Motion from 'react-motion/lib/Motion';
+import PropTypes from 'prop-types';
+
+const stylesToKeep = ['opacity', 'backgroundOpacity'];
+
+const extractValue = (value) => {
+  // This is either an object with a "val" property or it's a number
+  return (typeof value === 'object' && value && 'val' in value) ? value.val : value;
+};
+
+class ReducedMotion extends React.Component {
+
+  static propTypes = {
+    defaultStyle: PropTypes.object,
+    style: PropTypes.object,
+    children: PropTypes.func,
+  }
+
+  render() {
+
+    const { style, defaultStyle, children } = this.props;
+
+    Object.keys(style).forEach(key => {
+      if (stylesToKeep.includes(key)) {
+        return;
+      }
+      // If it's setting an x or height or scale or some other value, we need
+      // to preserve the end-state value without actually animating it
+      style[key] = defaultStyle[key] = extractValue(style[key]);
+    });
+
+    return (
+      <Motion style={style} defaultStyle={defaultStyle}>
+        {children}
+      </Motion>
+    );
+  }
+
+}
+
+export default ReducedMotion;
diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js
new file mode 100644
index 000000000..ef5d8b0ef
--- /dev/null
+++ b/app/javascript/mastodon/initial_state.js
@@ -0,0 +1,21 @@
+const element = document.getElementById('initial-state');
+const initialState = element && function () {
+  const result = JSON.parse(element.textContent);
+  try {
+    result.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
+  } catch (e) {
+    result.local_settings = {};
+  }
+  return result;
+}();
+
+const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop];
+
+export const reduceMotion = getMeta('reduce_motion');
+export const autoPlayGif = getMeta('auto_play_gif');
+export const unfollowModal = getMeta('unfollow_modal');
+export const boostModal = getMeta('boost_modal');
+export const deleteModal = getMeta('delete_modal');
+export const me = getMeta('me');
+
+export default initialState;
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index d99dacd59..2919928af 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -63,7 +63,7 @@
   "confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
   "confirmations.unfollow.confirm": "Unfollow",
   "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
-  "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 퍼가세요.",
+  "embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
   "embed.preview": "다음과 같이 표시됩니다:",
   "emoji_button.activity": "활동",
   "emoji_button.custom": "Custom",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 1e0849d95..d826423b5 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -31,7 +31,7 @@
   "column.favourites": "Favorits",
   "column.follow_requests": "Demandas d’abonament",
   "column.home": "Acuèlh",
-  "column.mutes": "Personas en silenci",
+  "column.mutes": "Personas rescondudas",
   "column.notifications": "Notificacions",
   "column.pins": "Tuts penjats",
   "column.public": "Flux public global",
@@ -55,12 +55,12 @@
   "confirmation_modal.cancel": "Anullar",
   "confirmations.block.confirm": "Blocar",
   "confirmations.block.message": "Sètz segur de voler blocar {name} ?",
-  "confirmations.delete.confirm": "Suprimir",
-  "confirmations.delete.message": "Sètz segur de voler suprimir l’estatut ?",
+  "confirmations.delete.confirm": "Escafar",
+  "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?",
   "confirmations.domain_block.confirm": "Amagar tot lo domeni",
   "confirmations.domain_block.message": "Sètz segur segur de voler blocar completament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.",
-  "confirmations.mute.confirm": "Metre en silenci",
-  "confirmations.mute.message": "Sètz segur de voler metre en silenci {name} ?",
+  "confirmations.mute.confirm": "Rescondre",
+  "confirmations.mute.message": "Sètz segur de voler rescondre {name} ?",
   "confirmations.unfollow.confirm": "Quitar de sègre",
   "confirmations.unfollow.message": "Volètz vertadièrament quitar de sègre {name} ?",
   "embed.instructions": "Embarcar aqueste estatut per lo far veire sus un site Internet en copiar lo còdi çai-jos.",
@@ -135,7 +135,7 @@
   "onboarding.page_five.public_timelines": "Lo flux local mòstra los estatuts publics del monde de vòstra instància, aquí {domain}. Lo flux federat mòstra los estatuts publics de la gent que los de {domain} sègon. Son los fluxes publics, un bon biais de trobar de mond.",
   "onboarding.page_four.home": "Lo flux d’acuèlh mòstra los estatuts del mond que seguètz.",
   "onboarding.page_four.notifications": "La colomna de notificacions vos fa veire quand qualqu’un interagís amb vos",
-  "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per bastir un malhum mai larg. Òm los apèla instàncias.",
+  "onboarding.page_one.federation": "Mastodon es un malhum de servidors independents que comunican per construire un malhum mai larg. Òm los apèla instàncias.",
   "onboarding.page_one.handle": "Sètz sus {domain}, doncas vòstre identificant complet es {handle}",
   "onboarding.page_one.welcome": "Benvengut a Mastodon !",
   "onboarding.page_six.admin": "Vòstre administrator d’instància es {admin}.",
@@ -159,11 +159,11 @@
   "privacy.public.short": "Public",
   "privacy.unlisted.long": "Mostrar pas dins los fluxes publics",
   "privacy.unlisted.short": "Pas-listat",
-  "relative_time.days": "fa {number}j",
-  "relative_time.hours": "fa {number}h",
+  "relative_time.days": "fa {number} d",
+  "relative_time.hours": "fa {number} h",
   "relative_time.just_now": "ara",
-  "relative_time.minutes": "fa {number} minutas",
-  "relative_time.seconds": "fa {number} segondas",
+  "relative_time.minutes": "fa {number} min",
+  "relative_time.seconds": "fa {number} s",
   "reply_indicator.cancel": "Anullar",
   "report.placeholder": "Comentaris addicionals",
   "report.submit": "Mandar",
@@ -197,7 +197,7 @@
   "status.share": "Partejar",
   "status.show_less": "Tornar plegar",
   "status.show_more": "Desplegar",
-  "status.unmute_conversation": "Conversacions amb silenci levat",
+  "status.unmute_conversation": "Tornar mostrar la conversacion",
   "status.unpin": "Tirar del perfil",
   "tabs_bar.compose": "Compausar",
   "tabs_bar.federated_timeline": "Flux public global",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index cf76f1b1f..b23a5e69f 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -89,7 +89,7 @@
   "follow_request.reject": "Odrzuć",
   "getting_started.appsshort": "Aplikacje",
   "getting_started.faq": "FAQ",
-  "getting_started.heading": "Naucz się korzystać",
+  "getting_started.heading": "Rozpocznij",
   "getting_started.open_source_notice": "Mastodon jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitHubie tutaj: {github}.",
   "getting_started.userguide": "Podręcznik użytkownika",
   "home.column_settings.advanced": "Zaawansowane",
@@ -174,7 +174,7 @@
   "search_popout.tips.status": "wpis",
   "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
   "search_popout.tips.user": "użytkownik",
-  "search_results.total": "{count, number} {count, plural, one {wynik} more {wyniki}}",
+  "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
   "standalone.public_title": "Spojrzenie w głąb…",
   "status.cannot_reblog": "Ten wpis nie może zostać podbity",
   "status.delete": "Usuń",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index ddb8b83f5..a04d1cc31 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -161,7 +161,7 @@
   "privacy.unlisted.short": "Não listada",
   "relative_time.days": "{number}d",
   "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
+  "relative_time.just_now": "agora",
   "relative_time.minutes": "{number}m",
   "relative_time.seconds": "{number}s",
   "reply_indicator.cancel": "Cancelar",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 827c815cf..bbdf34d2f 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -1,25 +1,27 @@
 {
   "account.block": "屏蔽 @{name}",
-  "account.block_domain": "隐藏一切来自 {domain} 的嘟文",
-  "account.disclaimer_full": "下列资料不一定完整。",
+  "account.block_domain": "隐藏来自 {domain} 的内容",
+  "account.disclaimer_full": "此处显示的信息可能不是全部内容。",
   "account.edit_profile": "修改个人资料",
   "account.follow": "关注",
   "account.followers": "关注者",
-  "account.follows": "正关注",
-  "account.follows_you": "关注你",
+  "account.follows": "正在关注",
+  "account.follows_you": "关注了你",
   "account.media": "媒体",
   "account.mention": "提及 @{name}",
-  "account.mute": "将 @{name} 静音",
+  "account.mute": "隐藏 @{name}",
+  "account.mute_notifications": "隐藏来自 @{name} 的通知",
+  "account.unmute_notifications": "不再隐藏来自 @{name} 的通知",
   "account.posts": "嘟文",
   "account.report": "举报 @{name}",
-  "account.requested": "等待审批",
-  "account.share": "分享 @{name}的个人资料",
-  "account.unblock": "解除对 @{name} 的屏蔽",
-  "account.unblock_domain": "不再隐藏 {domain}",
+  "account.requested": "正在等待对方同意。点击以取消发送关注请求",
+  "account.share": "分享 @{name} 的个人资料",
+  "account.unblock": "不再屏蔽 @{name}",
+  "account.unblock_domain": "不再隐藏来自 {domain} 的内容",
   "account.unfollow": "取消关注",
-  "account.unmute": "取消 @{name} 的静音",
+  "account.unmute": "不再隐藏 @{name}",
   "account.view_full_profile": "查看完整资料",
-  "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
+  "boost_modal.combo": "下次按住 {combo} 即可跳过此提示",
   "bundle_column_error.body": "载入组件出错。",
   "bundle_column_error.retry": "重试",
   "bundle_column_error.title": "网络错误",
@@ -31,79 +33,80 @@
   "column.favourites": "收藏过的嘟文",
   "column.follow_requests": "关注请求",
   "column.home": "主页",
-  "column.mutes": "被静音的用户",
+  "column.mutes": "被隐藏的用户",
   "column.notifications": "通知",
   "column.pins": "置顶嘟文",
   "column.public": "跨站公共时间轴",
   "column_back_button.label": "返回",
   "column_header.hide_settings": "隐藏设置",
-  "column_header.moveLeft_settings": "将栏左移",
-  "column_header.moveRight_settings": "将栏右移",
+  "column_header.moveLeft_settings": "将此栏左移",
+  "column_header.moveRight_settings": "将此栏右移",
   "column_header.pin": "固定",
   "column_header.show_settings": "显示设置",
-  "column_header.unpin": "取下",
+  "column_header.unpin": "取消固定",
   "column_subheading.navigation": "导航",
   "column_subheading.settings": "设置",
-  "compose_form.lock_disclaimer": "你的帐户没 {locked}. 任何人可以通过关注你来查看只有关注者可见的嘟文.",
+  "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以通过关注你来查看仅关注者可见的嘟文。",
   "compose_form.lock_disclaimer.lock": "被保护",
   "compose_form.placeholder": "在想啥?",
   "compose_form.publish": "嘟嘟",
   "compose_form.publish_loud": "{publish}!",
-  "compose_form.sensitive": "将媒体文件标示为“敏感内容”",
-  "compose_form.spoiler": "将部分文本藏于警告消息之后",
-  "compose_form.spoiler_placeholder": "敏感内容的警告消息",
+  "compose_form.sensitive": "将媒体文件标记为敏感内容",
+  "compose_form.spoiler": "折叠嘟文内容",
+  "compose_form.spoiler_placeholder": "折叠部分的警告消息",
   "confirmation_modal.cancel": "取消",
   "confirmations.block.confirm": "屏蔽",
-  "confirmations.block.message": "想好了,真的要屏蔽 {name}?",
+  "confirmations.block.message": "想好了,真的要屏蔽 {name}?",
   "confirmations.delete.confirm": "删除",
-  "confirmations.delete.message": "想好了,真的要删除这条嘟文?",
-  "confirmations.domain_block.confirm": "隐藏整个网站",
-  "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain} ?多数情况下,封锁或静音几个特定目标就好。",
-  "confirmations.mute.confirm": "静音",
-  "confirmations.mute.message": "想好了,真的要静音 {name}?",
+  "confirmations.delete.message": "想好了,真的要删除这条嘟文?",
+  "confirmations.domain_block.confirm": "隐藏整个网站的内容",
+  "confirmations.domain_block.message": "你真的真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户就应该能满足你的需要了。",
+  "confirmations.mute.confirm": "隐藏",
+  "confirmations.mute.message": "想好了,真的要隐藏 {name}?",
   "confirmations.unfollow.confirm": "取消关注",
-  "confirmations.unfollow.message": "确定要取消关注 {name}吗?",
-  "embed.instructions": "要内嵌此嘟文,请将以下代码贴进你的网站。",
-  "embed.preview": "到时大概长这样:",
+  "confirmations.unfollow.message": "确定要取消关注 {name} 吗?",
+  "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。",
+  "embed.preview": "它会像这样显示出来:",
   "emoji_button.activity": "活动",
-  "emoji_button.custom": "Custom",
+  "emoji_button.custom": "自定义",
   "emoji_button.flags": "旗帜",
   "emoji_button.food": "食物和饮料",
   "emoji_button.label": "加入表情符号",
   "emoji_button.nature": "自然",
-  "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
+  "emoji_button.not_found": "木有这个表情符号!(╯°□°)╯︵ ┻━┻",
   "emoji_button.objects": "物体",
   "emoji_button.people": "人物",
-  "emoji_button.recent": "Frequently used",
+  "emoji_button.recent": "常用",
   "emoji_button.search": "搜索…",
-  "emoji_button.search_results": "Search results",
+  "emoji_button.search_results": "搜索结果",
   "emoji_button.symbols": "符号",
-  "emoji_button.travel": "旅途和地点",
-  "empty_column.community": "本站时间轴暂时未有内容,快嘟几个来抢头香啊!",
-  "empty_column.hashtag": "这个标签暂时未有内容。",
+  "emoji_button.travel": "旅行和地点",
+  "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!",
+  "empty_column.hashtag": "这个话题标签下暂时没有内容。",
   "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
   "empty_column.home.public_timeline": "公共时间轴",
-  "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
-  "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务器实例的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
-  "follow_request.authorize": "批准",
+  "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。",
+  "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!",
+  "follow_request.authorize": "同意",
   "follow_request.reject": "拒绝",
-  "getting_started.appsshort": "Apps",
-  "getting_started.faq": "FAQ",
+  "getting_started.appsshort": "应用",
+  "getting_started.faq": "常见问题",
   "getting_started.heading": "开始使用",
-  "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。",
+  "getting_started.open_source_notice": "Mastodon 是一个开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。",
   "getting_started.userguide": "用户指南",
-  "home.column_settings.advanced": "高端",
-  "home.column_settings.basic": "基本",
-  "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
-  "home.column_settings.show_reblogs": "显示被转的嘟文",
-  "home.column_settings.show_replies": "显示回应嘟文",
-  "home.settings": "字段设置",
+  "home.column_settings.advanced": "高级设置",
+  "home.column_settings.basic": "基本设置",
+  "home.column_settings.filter_regex": "使用正则表达式(regex)过滤",
+  "home.column_settings.show_reblogs": "显示转嘟",
+  "home.column_settings.show_replies": "显示回复",
+  "home.settings": "栏目设置",
   "lightbox.close": "关闭",
   "lightbox.next": "下一步",
   "lightbox.previous": "上一步",
   "loading_indicator.label": "加载中……",
-  "media_gallery.toggle_visible": "打开或关上",
+  "media_gallery.toggle_visible": "切换显示/隐藏",
   "missing_indicator.label": "找不到内容",
+  "mute_modal.hide_notifications": "隐藏来自这个用户的通知",
   "navigation_bar.blocks": "被屏蔽的用户",
   "navigation_bar.community_timeline": "本站时间轴",
   "navigation_bar.edit_profile": "修改个人资料",
@@ -111,7 +114,7 @@
   "navigation_bar.follow_requests": "关注请求",
   "navigation_bar.info": "关于本站",
   "navigation_bar.logout": "注销",
-  "navigation_bar.mutes": "被静音的用户",
+  "navigation_bar.mutes": "被隐藏的用户",
   "navigation_bar.pins": "置顶嘟文",
   "navigation_bar.preferences": "首选项",
   "navigation_bar.public_timeline": "跨站公共时间轴",
@@ -119,9 +122,9 @@
   "notification.follow": "{name} 开始关注你",
   "notification.mention": "{name} 提及你",
   "notification.reblog": "{name} 转嘟了你的嘟文",
-  "notifications.clear": "清空通知纪录",
-  "notifications.clear_confirmation": "你确定要清空通知纪录吗?",
-  "notifications.column_settings.alert": "显示桌面通知",
+  "notifications.clear": "清空通知列表",
+  "notifications.clear_confirmation": "你确定要清空通知列表吗?",
+  "notifications.column_settings.alert": "桌面通知",
   "notifications.column_settings.favourite": "你的嘟文被收藏:",
   "notifications.column_settings.follow": "关注你:",
   "notifications.column_settings.mention": "提及你:",
@@ -132,90 +135,91 @@
   "notifications.column_settings.sound": "播放音效",
   "onboarding.done": "出发!",
   "onboarding.next": "下一步",
-  "onboarding.page_five.public_timelines": "本站时间轴显示来自 {domain} 的所有人的公共嘟文。 跨站公共时间轴显示 {domain} 上的各位关注的来自所有Mastodon服务器实例上的人发表的公共嘟文。这些就是寻人好去处的公共时间轴啦。",
-  "onboarding.page_four.home": "你的主时间轴上是你关注的用户的嘟文.",
-  "onboarding.page_four.notifications": "如果你和他人产生了互动,便会出现在通知列上啦~",
-  "onboarding.page_one.federation": "Mastodon是由一系列独立的服务器共同打造的强大的社交网络,我们将这些独立但又相互连接的服务器叫做服务器实例。",
-  "onboarding.page_one.handle": "你在 {domain}, {handle} 就是你的完整帐户名称。",
-  "onboarding.page_one.welcome": "欢迎来到 Mastodon!",
+  "onboarding.page_five.public_timelines": "本站时间轴显示的是由本站({domain})用户发布的所有公开嘟文。跨站公共时间轴显示的的是由本站用户关注对象所发布的所有公开嘟文。这些就是寻人好去处的公共时间轴啦。",
+  "onboarding.page_four.home": "你的主页上的时间轴上显示的是你关注对象的嘟文。",
+  "onboarding.page_four.notifications": "如果有人与你互动,便会出现在通知栏中哦~",
+  "onboarding.page_one.federation": "Mastodon 是由一系列独立的服务器共同打造的强大的社交网络,我们将这些各自独立但又相互连接的服务器叫做实例。",
+  "onboarding.page_one.handle": "你在 {domain},{handle} 就是你的完整帐户名称。",
+  "onboarding.page_one.welcome": "欢迎来到 Mastodon!",
   "onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员.",
-  "onboarding.page_six.almost_done": "差不多了…",
-  "onboarding.page_six.appetoot": "嗷呜~",
-  "onboarding.page_six.apps_available": "也有适用于 iOS, Android 和其它平台的 {apps} 咯~",
-  "onboarding.page_six.github": "Mastodon 是自由的开放源代码软件。欢迎来 {github} 报告问题,提交功能请求,或者贡献代码 :-)",
+  "onboarding.page_six.almost_done": "差不多了……",
+  "onboarding.page_six.appetoot": "嗷呜~",
+  "onboarding.page_six.apps_available": "我们还有适用于 iOS、Android 和其它平台的{apps}哦~",
+  "onboarding.page_six.github": "Mastodon 是自由的开源软件。欢迎前往 {github} 反馈问题、提出对新功能的建议或贡献代码 :-)",
   "onboarding.page_six.guidelines": "社区指南",
-  "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的 {guidelines}!",
-  "onboarding.page_six.various_app": "移动应用程序",
-  "onboarding.page_three.profile": "修改你的个人资料,比如头像、简介、和昵称等等。在那还可以找到其它首选项。",
-  "onboarding.page_three.search": "用搜索来找人和标签吧,比如 {illustration} 或者 {introductions}。想找其它服务器实例上的人,用完整帐户名称(用户名@域名)啦。",
-  "onboarding.page_two.compose": "从这里开始嘟!上面的按钮提供了上传图片,修改隐私设置和提示敏感内容等多种功能。.",
-  "onboarding.skip": "好啦好啦我知道啦",
-  "privacy.change": "调整隐私设置",
-  "privacy.direct.long": "只有提及的用户能看到",
-  "privacy.direct.short": "私人消息",
-  "privacy.private.long": "只有关注你用户能看到",
-  "privacy.private.short": "关注者",
-  "privacy.public.long": "在公共时间轴显示",
-  "privacy.public.short": "公共",
-  "privacy.unlisted.long": "公开,但不在公共时间轴显示",
-  "privacy.unlisted.short": "公开",
-  "relative_time.days": "{number}d",
-  "relative_time.hours": "{number}h",
-  "relative_time.just_now": "now",
-  "relative_time.minutes": "{number}m",
-  "relative_time.seconds": "{number}s",
+  "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的{guidelines}!",
+  "onboarding.page_six.various_app": "移动设备应用",
+  "onboarding.page_three.profile": "你可以修改你的个人资料,比如头像、简介和昵称等偏好设置。",
+  "onboarding.page_three.search": "你可以通过搜索功能寻找用户和话题标签,比如{illustration}或者{introductions}。如果你想搜索其他实例上的用户,就需要输入完整帐户名称(用户名@域名)哦。",
+  "onboarding.page_two.compose": "在撰写栏中开始嘟嘟吧!下方的按钮分别用来上传图片,修改嘟文可见范围,以及添加警告信息。",
+  "onboarding.skip": "跳过",
+  "privacy.change": "设置嘟文可见范围",
+  "privacy.direct.long": "只有被提及的用户能看到",
+  "privacy.direct.short": "私信",
+  "privacy.private.long": "只有关注你的用户能看到",
+  "privacy.private.short": "仅关注者",
+  "privacy.public.long": "所有人可见,并会出现在公共时间轴上",
+  "privacy.public.short": "公开",
+  "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上",
+  "privacy.unlisted.short": "不公开",
+  "relative_time.days": "{number} 天",
+  "relative_time.hours": "{number} 时",
+  "relative_time.just_now": "刚刚",
+  "relative_time.minutes": "{number} 分",
+  "relative_time.seconds": "{number} 秒",
   "reply_indicator.cancel": "取消",
-  "report.placeholder": "额外消息",
+  "report.placeholder": "附言",
   "report.submit": "提交",
-  "report.target": "Reporting",
+  "report.target": "举报 {target}",
   "search.placeholder": "搜索",
-  "search_popout.search_format": "Advanced search format",
-  "search_popout.tips.hashtag": "hashtag",
-  "search_popout.tips.status": "status",
-  "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
-  "search_popout.tips.user": "user",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+  "search_popout.search_format": "高级搜索格式",
+  "search_popout.tips.hashtag": "话题标签",
+  "search_popout.tips.status": "嘟文",
+  "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签",
+  "search_popout.tips.user": "用户",
+  "search_results.total": "共 {count, number} 个结果",
   "standalone.public_title": "大家都在干啥?",
-  "status.cannot_reblog": "没法转嘟这条嘟文啦……",
+  "status.cannot_reblog": "无法转嘟这条嘟文",
   "status.delete": "删除",
   "status.embed": "嵌入",
   "status.favourite": "收藏",
   "status.load_more": "加载更多",
   "status.media_hidden": "隐藏媒体内容",
   "status.mention": "提及 @{name}",
-  "status.more": "More",
-  "status.mute_conversation": "静音对话",
+  "status.more": "更多",
+  "status.mute_conversation": "隐藏此对话",
   "status.open": "展开嘟文",
-  "status.pin": "置顶到资料",
+  "status.pin": "在个人资料页面置顶",
   "status.reblog": "转嘟",
-  "status.reblogged_by": "{name} 转嘟",
-  "status.reply": "回应",
-  "status.replyAll": "回应整串",
+  "status.reblogged_by": "{name} 转嘟了",
+  "status.reply": "回复",
+  "status.replyAll": "回复所有人",
   "status.report": "举报 @{name}",
   "status.sensitive_toggle": "点击显示",
   "status.sensitive_warning": "敏感内容",
-  "status.share": "Share",
-  "status.show_less": "减少显示",
-  "status.show_more": "显示更多",
-  "status.unmute_conversation": "解禁对话",
-  "status.unpin": "解除置顶",
+  "status.share": "分享",
+  "status.show_less": "隐藏内容",
+  "status.show_more": "显示内容",
+  "status.unmute_conversation": "不再隐藏此对话",
+  "status.unpin": "在个人资料页面取消置顶",
   "tabs_bar.compose": "撰写",
   "tabs_bar.federated_timeline": "跨站",
   "tabs_bar.home": "主页",
   "tabs_bar.local_timeline": "本站",
   "tabs_bar.notifications": "通知",
-  "upload_area.title": "将文件拖放至此上传",
+  "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。",
+  "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件",
-  "upload_form.description": "Describe for the visually impaired",
-  "upload_form.undo": "还原",
-  "upload_progress.label": "上传中……",
-  "video.close": "关闭影片",
+  "upload_form.description": "为视觉障碍人士添加文字说明",
+  "upload_form.undo": "取消上传",
+  "upload_progress.label": "上传中…",
+  "video.close": "关闭视频",
   "video.exit_fullscreen": "退出全屏",
-  "video.expand": "展开影片",
+  "video.expand": "展开视频",
   "video.fullscreen": "全屏",
-  "video.hide": "隐藏影片",
+  "video.hide": "隐藏视频",
   "video.mute": "静音",
   "video.pause": "暂停",
   "video.play": "播放",
-  "video.unmute": "解除静音"
+  "video.unmute": "取消静音"
 }
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 251a40144..5d0acbd60 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -33,6 +33,7 @@ import { TIMELINE_DELETE } from '../actions/timelines';
 import { STORE_HYDRATE } from '../actions/store';
 import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
 import uuid from '../uuid';
+import { me } from '../initial_state';
 
 const initialState = ImmutableMap({
   mounted: false,
@@ -54,7 +55,6 @@ const initialState = ImmutableMap({
   media_attachments: ImmutableList(),
   suggestion_token: null,
   suggestions: ImmutableList(),
-  me: null,
   default_advanced_options: ImmutableMap({
     do_not_federate: false,
   }),
@@ -77,7 +77,6 @@ const initialState = ImmutableMap({
 
 function statusToTextMentions(state, status) {
   let set = ImmutableOrderedSet([]);
-  let me  = state.get('me');
 
   if (status.getIn(['account', 'id']) !== me) {
     set = set.add(`@${status.getIn(['account', 'acct'])} `);
diff --git a/app/javascript/mastodon/reducers/custom_emojis.js b/app/javascript/mastodon/reducers/custom_emojis.js
index f2a8ca5d2..307bcc7dc 100644
--- a/app/javascript/mastodon/reducers/custom_emojis.js
+++ b/app/javascript/mastodon/reducers/custom_emojis.js
@@ -8,7 +8,7 @@ const initialState = ImmutableList();
 export default function custom_emojis(state = initialState, action) {
   switch(action.type) {
   case STORE_HYDRATE:
-    emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', []), action.state.getIn(['meta', 'auto_play_gif'], false)) });
+    emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', [])) });
     return action.state.get('custom_emojis');
   default:
     return state;
diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js
index 119ef9d8f..36a5a1c35 100644
--- a/app/javascript/mastodon/reducers/meta.js
+++ b/app/javascript/mastodon/reducers/meta.js
@@ -4,7 +4,6 @@ import { Map as ImmutableMap } from 'immutable';
 const initialState = ImmutableMap({
   streaming_api_base_url: null,
   access_token: null,
-  me: null,
 });
 
 export default function meta(state = initialState, action) {
diff --git a/app/javascript/mastodon/reducers/mutes.js b/app/javascript/mastodon/reducers/mutes.js
index 496e6846a..a96232dbd 100644
--- a/app/javascript/mastodon/reducers/mutes.js
+++ b/app/javascript/mastodon/reducers/mutes.js
@@ -22,7 +22,7 @@ export default function mutes(state = initialState, action) {
       state.setIn(['new', 'notifications'], true);
     });
   case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
-    return state.setIn(['new', 'notifications'], !state.getIn(['new', 'notifications']));
+    return state.updateIn(['new', 'notifications'], (old) => !old);
   default:
     return state;
   }
diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js
index 4b36082b2..36c68ffc5 100644
--- a/app/javascript/mastodon/stream.js
+++ b/app/javascript/mastodon/stream.js
@@ -1,5 +1,66 @@
 import WebSocketClient from 'websocket.js';
 
+export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
+  return (dispatch, getState) => {
+    const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
+    const accessToken = getState().getIn(['meta', 'access_token']);
+    const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
+    let polling = null;
+
+    const setupPolling = () => {
+      polling = setInterval(() => {
+        pollingRefresh(dispatch);
+      }, 20000);
+    };
+
+    const clearPolling = () => {
+      if (polling) {
+        clearInterval(polling);
+        polling = null;
+      }
+    };
+
+    const subscription = getStream(streamingAPIBaseURL, accessToken, path, {
+      connected () {
+        if (pollingRefresh) {
+          clearPolling();
+        }
+        onConnect();
+      },
+
+      disconnected () {
+        if (pollingRefresh) {
+          setupPolling();
+        }
+        onDisconnect();
+      },
+
+      received (data) {
+        onReceive(data);
+      },
+
+      reconnected () {
+        if (pollingRefresh) {
+          clearPolling();
+          pollingRefresh(dispatch);
+        }
+        onConnect();
+      },
+
+    });
+
+    const disconnect = () => {
+      if (subscription) {
+        subscription.close();
+      }
+      clearPolling();
+    };
+
+    return disconnect;
+  };
+}
+
+
 export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
   const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
 
diff --git a/app/javascript/packs/custom.js b/app/javascript/packs/custom.js
deleted file mode 100644
index 4db2964f6..000000000
--- a/app/javascript/packs/custom.js
+++ /dev/null
@@ -1 +0,0 @@
-require('../styles/custom.scss');
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index b00dd8c1e..2cf98c642 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -571,7 +571,19 @@
   font-size: 12px;
   line-height: 12px;
   font-weight: 500;
-  color: $success-green;
-  background-color: rgba($success-green, 0.1);
-  border: 1px solid rgba($success-green, 0.5);
+  color: $ui-secondary-color;
+  background-color: rgba($ui-secondary-color, 0.1);
+  border: 1px solid rgba($ui-secondary-color, 0.5);
+
+  &.moderator {
+    color: $success-green;
+    background-color: rgba($success-green, 0.1);
+    border-color: rgba($success-green, 0.5);
+  }
+
+  &.admin {
+    color: lighten($error-red, 12%);
+    background-color: rgba(lighten($error-red, 12%), 0.1);
+    border-color: rgba(lighten($error-red, 12%), 0.5);
+  }
 }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 2506bbe62..6a6d1bdca 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -253,6 +253,15 @@
   width: 0;
   height: 0;
   position: absolute;
+
+  img,
+  svg {
+    margin: 0 !important;
+    border: 0 !important;
+    padding: 0 !important;
+    width: 0 !important;
+    height: 0 !important;
+  }
 }
 
 .ellipsis {
@@ -555,6 +564,7 @@
   font-weight: 400;
   overflow: visible;
   white-space: pre-wrap;
+  padding-top: 5px;
 
   &.status__content--with-spoiler {
     white-space: normal;
@@ -565,8 +575,9 @@
   }
 
   .emojione {
-    width: 18px;
-    height: 18px;
+    width: 20px;
+    height: 20px;
+    margin: -5px 0 0;
   }
 
   p {
@@ -671,7 +682,7 @@
     outline: 0;
     background: lighten($ui-base-color, 4%);
 
-    &.status-direct {
+    .status.status-direct {
       background: lighten($ui-base-color, 12%);
     }
 
@@ -690,6 +701,12 @@
   border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: default;
 
+  @supports (-ms-overflow-style: -ms-autohiding-scrollbar) {
+    // Add margin to avoid Edge auto-hiding scrollbar appearing over content.
+    // On Edge 16 this is 16px and Edge <=15 it's 12px, so aim for 16px.
+    padding-right: 26px; // 10px + 16px
+  }
+
   @keyframes fade {
     0% { opacity: 0; }
     100% { opacity: 1; }
@@ -920,8 +937,9 @@
     line-height: 24px;
 
     .emojione {
-      width: 22px;
-      height: 22px;
+      width: 24px;
+      height: 24px;
+      margin: -5px 0 0;
     }
   }
 
@@ -2908,7 +2926,7 @@ button.icon-button.active i.fa-retweet {
   color: $primary-text-color;
   position: absolute;
   top: 10px;
-  right: 10px;
+  left: 10px;
   opacity: 0.7;
   display: inline-block;
   vertical-align: top;
@@ -2923,7 +2941,7 @@ button.icon-button.active i.fa-retweet {
 .account--action-button {
   position: absolute;
   top: 10px;
-  left: 20px;
+  right: 20px;
 }
 
 .setting-toggle {
@@ -3973,6 +3991,14 @@ button.icon-button.active i.fa-retweet {
   }
 }
 
+.mute-modal {
+  line-height: 24px;
+}
+
+.mute-modal .react-toggle {
+  vertical-align: middle;
+}
+
 .report-modal__statuses,
 .report-modal__comment {
   padding: 10px;
diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss
index 15ff84912..0bf9daafd 100644
--- a/app/javascript/styles/mastodon/landing_strip.scss
+++ b/app/javascript/styles/mastodon/landing_strip.scss
@@ -1,4 +1,5 @@
-.landing-strip {
+.landing-strip,
+.memoriam-strip {
   background: rgba(darken($ui-base-color, 7%), 0.8);
   color: $ui-primary-color;
   font-weight: 400;
@@ -29,3 +30,7 @@
     margin-bottom: 0;
   }
 }
+
+.memoriam-strip {
+  background: rgba($base-shadow-color, 0.7);
+}
diff --git a/app/javascript/themes/default/theme.yml b/app/javascript/themes/default/theme.yml
index 6a7a872b4..0b262cc82 100644
--- a/app/javascript/themes/default/theme.yml
+++ b/app/javascript/themes/default/theme.yml
@@ -1,9 +1,18 @@
-#  (REQUIRED) Name must be unique across all installed themes.
-name: default
-
 #  (REQUIRED) The location of the pack file inside `pack_directory`.
 pack: application.js
 
 #  (OPTIONAL) The directory which contains the pack file.
-#  Defaults to the theme directory (`app/javascript/themes/[theme]`).
+#  Defaults to the theme directory (`app/javascript/themes/[theme]`),
+#  but in the case of the vanilla Mastodon theme the pack file is
+#  somewhere else.
 pack_directory: app/javascript/packs
+
+#  (OPTIONAL) Additional javascript resources to preload, for use with
+#  lazy-loaded components. It is **STRONGLY RECOMMENDED** that you
+#  derive these pathnames from `themes/[your-theme]` to ensure that
+#  they stay unique. (Of course, vanilla doesn't do this ^^;;)
+preload:
+- features/getting_started
+- features/compose
+- features/home_timeline
+- features/notifications
diff --git a/app/javascript/themes/spin/pack.js b/app/javascript/themes/spin/pack.js
deleted file mode 100644
index b11ac4802..000000000
--- a/app/javascript/themes/spin/pack.js
+++ /dev/null
@@ -1,2 +0,0 @@
-import '../../packs/application';
-import './style.scss';
diff --git a/app/javascript/themes/spin/style.scss b/app/javascript/themes/spin/style.scss
deleted file mode 100644
index 1a9381fd0..000000000
--- a/app/javascript/themes/spin/style.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-:root:root:root {
-  .button, .icon-button, .emoji-button, .account__avatar, .account__avatar-overlay {
-    animation: spin 4s linear infinite;
-  }
-}
-
-@keyframes spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}
diff --git a/app/javascript/themes/spin/theme.yml b/app/javascript/themes/spin/theme.yml
deleted file mode 100644
index a684997dc..000000000
--- a/app/javascript/themes/spin/theme.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-name: spin
-pack: pack.js
\ No newline at end of file
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 376684c00..66e4f7c5e 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -173,7 +173,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   end
 
   def language_from_content
-    return nil unless language_map?
+    return LanguageDetector.instance.detect(text_from_content, @account) unless language_map?
     @object['contentMap'].keys.first
   end
 
diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb
index 957364293..738ec89a0 100644
--- a/app/lib/extractor.rb
+++ b/app/lib/extractor.rb
@@ -5,7 +5,8 @@ module Extractor
 
   module_function
 
-  def extract_mentions_or_lists_with_indices(text) # :yields: username, list_slug, start, end
+  # :yields: username, list_slug, start, end
+  def extract_mentions_or_lists_with_indices(text)
     return [] unless text =~ Twitter::Regex[:at_signs]
 
     possible_entries = []
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index a99606e1b..5d7f47c6f 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -26,34 +26,42 @@ class FeedManager
     end
   end
 
-  def push(timeline_type, account, status)
-    return false unless add_to_feed(timeline_type, account, status)
-
-    trim(timeline_type, account.id)
-
-    PushUpdateWorker.perform_async(account.id, status.id) if push_update_required?(timeline_type, account.id)
-
+  def push_to_home(account, status)
+    return false unless add_to_feed(:home, account.id, status)
+    trim(:home, account.id)
+    PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
     true
   end
 
-  def unpush(timeline_type, account, status)
-    return false unless remove_from_feed(timeline_type, account, status)
+  def unpush_from_home(account, status)
+    return false unless remove_from_feed(:home, account.id, status)
+    Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
+    true
+  end
 
-    payload = Oj.dump(event: :delete, payload: status.id.to_s)
-    Redis.current.publish("timeline:#{account.id}", payload)
+  def push_to_list(list, status)
+    return false unless add_to_feed(:list, list.id, status)
+    trim(:list, list.id)
+    PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
+    true
+  end
 
+  def unpush_from_list(list, status)
+    return false unless remove_from_feed(:list, list.id, status)
+    Redis.current.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
     true
   end
 
   def trim(type, account_id)
     timeline_key = key(type, account_id)
-    reblog_key = key(type, account_id, 'reblogs')
+    reblog_key   = key(type, account_id, 'reblogs')
+
     # Remove any items past the MAX_ITEMS'th entry in our feed
     redis.zremrangebyrank(timeline_key, '0', (-(FeedManager::MAX_ITEMS + 1)).to_s)
 
     # Get the score of the REBLOG_FALLOFF'th item in our feed, and stop
     # tracking anything after it for deduplication purposes.
-    falloff_rank = FeedManager::REBLOG_FALLOFF - 1
+    falloff_rank  = FeedManager::REBLOG_FALLOFF - 1
     falloff_range = redis.zrevrange(timeline_key, falloff_rank, falloff_rank, with_scores: true)
     falloff_score = falloff_range&.first&.last&.to_i || 0
 
@@ -69,10 +77,6 @@ class FeedManager
     end
   end
 
-  def push_update_required?(timeline_type, account_id)
-    timeline_type != :home || redis.get("subscribed:timeline:#{account_id}").present?
-  end
-
   def merge_into_timeline(from_account, into_account)
     timeline_key = key(:home, into_account.id)
     query        = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
@@ -84,28 +88,28 @@ class FeedManager
 
     query.each do |status|
       next if status.direct_visibility? || filter?(:home, status, into_account)
-      add_to_feed(:home, into_account, status)
+      add_to_feed(:home, into_account.id, status)
     end
 
     trim(:home, into_account.id)
   end
 
   def unmerge_from_timeline(from_account, into_account)
-    timeline_key = key(:home, into_account.id)
+    timeline_key      = key(:home, into_account.id)
     oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
 
     from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_home_score).reorder(nil).find_each do |status|
-      remove_from_feed(:home, into_account, status)
+      remove_from_feed(:home, into_account.id, status)
     end
   end
 
   def clear_from_timeline(account, target_account)
-    timeline_key = key(:home, account.id)
+    timeline_key        = key(:home, account.id)
     timeline_status_ids = redis.zrange(timeline_key, 0, -1)
-    target_statuses = Status.where(id: timeline_status_ids, account: target_account)
+    target_statuses     = Status.where(id: timeline_status_ids, account: target_account)
 
     target_statuses.each do |status|
-      unpush(:home, account, status)
+      unpush_from_home(account, status)
     end
   end
 
@@ -122,7 +126,7 @@ class FeedManager
 
       statuses.each do |status|
         next if filter_from_home?(status, account)
-        added += 1 if add_to_feed(:home, account, status)
+        added += 1 if add_to_feed(:home, account.id, status)
       end
 
       break unless added.zero?
@@ -137,6 +141,10 @@ class FeedManager
     Redis.current
   end
 
+  def push_update_required?(timeline_id)
+    redis.exists("subscribed:#{timeline_id}")
+  end
+
   def filter_from_home?(status, receiver_id)
     return false if receiver_id == status.account_id
     return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
@@ -207,9 +215,9 @@ class FeedManager
   # added, and false if it was not added to the feed. Note that this is
   # an internal helper: callers must call trim or push updates if
   # either action is appropriate.
-  def add_to_feed(timeline_type, account, status)
-    timeline_key = key(timeline_type, account.id)
-    reblog_key   = key(timeline_type, account.id, 'reblogs')
+  def add_to_feed(timeline_type, account_id, status)
+    timeline_key = key(timeline_type, account_id)
+    reblog_key   = key(timeline_type, account_id, 'reblogs')
 
     if status.reblog?
       # If the original status or a reblog of it is within
@@ -220,6 +228,7 @@ class FeedManager
       return false if !rank.nil? && rank < FeedManager::REBLOG_FALLOFF
 
       reblog_rank = redis.zrevrank(reblog_key, status.reblog_of_id)
+
       if reblog_rank.nil?
         # This is not something we've already seen reblogged, so we
         # can just add it to the feed (and note that we're
@@ -230,7 +239,7 @@ class FeedManager
         # Another reblog of the same status was already in the
         # REBLOG_FALLOFF most recent statuses, so we note that this
         # is an "extra" reblog, by storing it in reblog_set_key.
-        reblog_set_key = key(timeline_type, account.id, "reblogs:#{status.reblog_of_id}")
+        reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
         redis.sadd(reblog_set_key, status.id)
         return false
       end
@@ -245,8 +254,8 @@ class FeedManager
   # with reblogs, and returning true if a status was removed. As with
   # `add_to_feed`, this does not trigger push updates, so callers must
   # do so if appropriate.
-  def remove_from_feed(timeline_type, account, status)
-    timeline_key = key(timeline_type, account.id)
+  def remove_from_feed(timeline_type, account_id, status)
+    timeline_key = key(timeline_type, account_id)
 
     if status.reblog?
       # 1. If the reblogging status is not in the feed, stop.
@@ -254,7 +263,7 @@ class FeedManager
       return false if status_rank.nil?
 
       # 2. Remove reblog from set of this status's reblogs.
-      reblog_set_key = key(timeline_type, account.id, "reblogs:#{status.reblog_of_id}")
+      reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
 
       redis.srem(reblog_set_key, status.id)
       # 3. Re-insert another reblog or original into the feed if one
@@ -269,7 +278,7 @@ class FeedManager
       # (outside conditional)
     else
       # If the original is getting deleted, no use for reblog references
-      redis.del(key(timeline_type, account.id, "reblogs:#{status.id}"))
+      redis.del(key(timeline_type, account_id, "reblogs:#{status.id}"))
     end
 
     redis.zrem(timeline_key, status.id)
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 57f105da7..733a1c4b7 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -89,20 +89,28 @@ class Formatter
     end
   end
 
+  def count_tag_nesting(tag)
+    if tag[1] == '/' then -1
+    elsif tag[-2] == '/' then 0
+    else 1
+    end
+  end
+
   def encode_custom_emojis(html, emojis)
     return html if emojis.empty?
 
     emoji_map = emojis.map { |e| [e.shortcode, full_asset_url(e.image.url(:static))] }.to_h
 
     i                     = -1
-    inside_tag            = false
+    tag_open_index        = nil
     inside_shortname      = false
     shortname_start_index = -1
+    invisible_depth       = 0
 
     while i + 1 < html.size
       i += 1
 
-      if inside_shortname && html[i] == ':'
+      if invisible_depth.zero? && inside_shortname && html[i] == ':'
         shortcode = html[shortname_start_index + 1..i - 1]
         emoji     = emoji_map[shortcode]
 
@@ -116,12 +124,18 @@ class Formatter
         end
 
         inside_shortname = false
-      elsif inside_tag && html[i] == '>'
-        inside_tag = false
+      elsif tag_open_index && html[i] == '>'
+        tag = html[tag_open_index..i]
+        tag_open_index = nil
+        if invisible_depth.positive?
+          invisible_depth += count_tag_nesting(tag)
+        elsif tag == '<span class="invisible">'
+          invisible_depth = 1
+        end
       elsif html[i] == '<'
-        inside_tag       = true
+        tag_open_index   = i
         inside_shortname = false
-      elsif !inside_tag && html[i] == ':'
+      elsif !tag_open_index && html[i] == ':'
         inside_shortname      = true
         shortname_start_index = i
       end
diff --git a/app/lib/language_detector.rb b/app/lib/language_detector.rb
index a42460e10..c6f52f0c7 100644
--- a/app/lib/language_detector.rb
+++ b/app/lib/language_detector.rb
@@ -38,12 +38,31 @@ class LanguageDetector
   end
 
   def simplify_text(text)
-    text.dup.tap do |new_text|
-      new_text.gsub!(FetchLinkCardService::URL_PATTERN, '')
-      new_text.gsub!(Account::MENTION_RE, '')
-      new_text.gsub!(Tag::HASHTAG_RE, '')
-      new_text.gsub!(/\s+/, ' ')
-    end
+    new_text = remove_html(text)
+    new_text.gsub!(FetchLinkCardService::URL_PATTERN, '')
+    new_text.gsub!(Account::MENTION_RE, '')
+    new_text.gsub!(Tag::HASHTAG_RE, '')
+    new_text.gsub!(/:#{CustomEmoji::SHORTCODE_RE_FRAGMENT}:/, '')
+    new_text.gsub!(/\s+/, ' ')
+    new_text
+  end
+
+  def new_scrubber
+    scrubber = Rails::Html::PermitScrubber.new
+    scrubber.tags = %w(br p)
+    scrubber
+  end
+
+  def scrubber
+    @scrubber ||= new_scrubber
+  end
+
+  def remove_html(text)
+    text = Loofah.fragment(text).scrub!(scrubber).to_s
+    text.gsub!('<br>', "\n")
+    text.gsub!('</p><p>', "\n\n")
+    text.gsub!(/(^<p>|<\/p>$)/, '')
+    text
   end
 
   def default_locale(account)
diff --git a/app/lib/themes.rb b/app/lib/themes.rb
index 2dd188297..f7ec22fd2 100644
--- a/app/lib/themes.rb
+++ b/app/lib/themes.rb
@@ -10,13 +10,18 @@ class Themes
     result = Hash.new
     Dir.glob(Rails.root.join('app', 'javascript', 'themes', '*', 'theme.yml')) do |path|
       data = YAML.load_file(path)
-      if data['pack'] && data['name']
-        result[data['name']] = data
+      name = File.basename(File.dirname(path))
+      if data['pack']
+        result[name] = data
       end
     end
     @conf = result
   end
 
+  def get(name)
+    @conf[name]
+  end
+
   def names
     @conf.keys
   end
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 80c9d8ccf..d79f26366 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -7,6 +7,8 @@ class NotificationMailer < ApplicationMailer
     @me     = recipient
     @status = notification.target_status
 
+    return if @me.user.disabled?
+
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
       mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
@@ -17,6 +19,8 @@ class NotificationMailer < ApplicationMailer
     @me      = recipient
     @account = notification.from_account
 
+    return if @me.user.disabled?
+
     locale_for_account(@me) do
       mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
     end
@@ -27,6 +31,8 @@ class NotificationMailer < ApplicationMailer
     @account = notification.from_account
     @status  = notification.target_status
 
+    return if @me.user.disabled?
+
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
       mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
@@ -38,6 +44,8 @@ class NotificationMailer < ApplicationMailer
     @account = notification.from_account
     @status  = notification.target_status
 
+    return if @me.user.disabled?
+
     locale_for_account(@me) do
       thread_by_conversation(@status.conversation)
       mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
@@ -48,6 +56,8 @@ class NotificationMailer < ApplicationMailer
     @me      = recipient
     @account = notification.from_account
 
+    return if @me.user.disabled?
+
     locale_for_account(@me) do
       mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
     end
@@ -59,15 +69,11 @@ class NotificationMailer < ApplicationMailer
     @notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since)
     @follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count
 
-    return if @notifications.empty?
+    return if @me.user.disabled? || @notifications.empty?
 
     locale_for_account(@me) do
       mail to: @me.user.email,
-           subject: I18n.t(
-             :subject,
-             scope: [:notification_mailer, :digest],
-             count: @notifications.size
-           )
+           subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications.size)
     end
   end
 
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index c475a9911..bdb29ebad 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -10,6 +10,8 @@ class UserMailer < Devise::Mailer
     @token    = token
     @instance = Rails.configuration.x.local_domain
 
+    return if @resource.disabled?
+
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance)
     end
@@ -20,6 +22,8 @@ class UserMailer < Devise::Mailer
     @token    = token
     @instance = Rails.configuration.x.local_domain
 
+    return if @resource.disabled?
+
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
     end
@@ -29,6 +33,8 @@ class UserMailer < Devise::Mailer
     @resource = user
     @instance = Rails.configuration.x.local_domain
 
+    return if @resource.disabled?
+
     I18n.with_locale(@resource.locale || I18n.default_locale) do
       mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
     end
diff --git a/app/models/account.rb b/app/models/account.rb
index 85684c259..a4b8e1c0b 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -41,10 +41,11 @@
 #  shared_inbox_url        :string           default(""), not null
 #  followers_url           :string           default(""), not null
 #  protocol                :integer          default("ostatus"), not null
+#  memorial                :boolean          default(FALSE), not null
 #
 
 class Account < ApplicationRecord
-  MENTION_RE = /(?:^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
+  MENTION_RE = /(?<=^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
 
   include AccountAvatar
   include AccountFinderConcern
@@ -52,6 +53,7 @@ class Account < ApplicationRecord
   include AccountInteractions
   include Attachmentable
   include Remotable
+  include Paginable
 
   MAX_NOTE_LENGTH = 500
 
@@ -96,6 +98,10 @@ class Account < ApplicationRecord
   has_many :account_moderation_notes, dependent: :destroy
   has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy
 
+  # Lists
+  has_many :list_accounts, inverse_of: :account, dependent: :destroy
+  has_many :lists, through: :list_accounts
+
   scope :remote, -> { where.not(domain: nil) }
   scope :local, -> { where(domain: nil) }
   scope :without_followers, -> { where(followers_count: 0) }
@@ -116,6 +122,8 @@ class Account < ApplicationRecord
            :current_sign_in_at,
            :confirmed?,
            :admin?,
+           :moderator?,
+           :staff?,
            :locale,
            to: :user,
            prefix: true,
@@ -152,6 +160,20 @@ class Account < ApplicationRecord
     ResolveRemoteAccountService.new.call(acct)
   end
 
+  def unsuspend!
+    transaction do
+      user&.enable! if local?
+      update!(suspended: false)
+    end
+  end
+
+  def memorialize!
+    transaction do
+      user&.disable! if local?
+      update!(memorial: true)
+    end
+  end
+
   def keypair
     @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
   end
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index fb695e473..35810b6c2 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -3,11 +3,11 @@
 #
 # Table name: account_domain_blocks
 #
+#  id         :integer          not null, primary key
 #  domain     :string
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #  account_id :integer
-#  id         :integer          not null, primary key
 #
 
 class AccountDomainBlock < ApplicationRecord
diff --git a/app/models/block.rb b/app/models/block.rb
index a913782ed..284abfe4c 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -3,10 +3,10 @@
 #
 # Table name: blocks
 #
+#  id                :integer          not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
 #  account_id        :integer          not null
-#  id                :integer          not null, primary key
 #  target_account_id :integer          not null
 #
 
diff --git a/app/models/concerns/account_finder_concern.rb b/app/models/concerns/account_finder_concern.rb
index 561c7ab9f..2e8a7fb37 100644
--- a/app/models/concerns/account_finder_concern.rb
+++ b/app/models/concerns/account_finder_concern.rb
@@ -44,7 +44,7 @@ module AccountFinderConcern
     end
 
     def with_usernames
-      Account.where.not(username: [nil, ''])
+      Account.where.not(username: '')
     end
 
     def matching_username
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index a68f7c3d8..c41f92581 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -23,7 +23,7 @@ module AccountInteractions
     def muting_map(target_account_ids, account_id)
       Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
         mapping[mute.target_account_id] = {
-          notifications: mute.hide_notifications?
+          notifications: mute.hide_notifications?,
         }
       end
     end
@@ -91,8 +91,7 @@ module AccountInteractions
     mute = mute_relationships.create_with(hide_notifications: notifications).find_or_create_by!(target_account: other_account)
     # When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
     if mute.hide_notifications? != notifications
-      mute.hide_notifications = notifications
-      mute.save!
+      mute.update!(hide_notifications: notifications)
     end
   end
 
diff --git a/app/models/conversation_mute.rb b/app/models/conversation_mute.rb
index 8d2399adf..248cdfe6e 100644
--- a/app/models/conversation_mute.rb
+++ b/app/models/conversation_mute.rb
@@ -3,9 +3,9 @@
 #
 # Table name: conversation_mutes
 #
+#  id              :integer          not null, primary key
 #  conversation_id :integer          not null
 #  account_id      :integer          not null
-#  id              :integer          not null, primary key
 #
 
 class ConversationMute < ApplicationRecord
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 28b6a2b0b..a77b53c98 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -25,6 +25,8 @@ class CustomEmoji < ApplicationRecord
     :(#{SHORTCODE_RE_FRAGMENT}):
     (?=[^[:alnum:]:]|$)/x
 
+  has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode
+
   has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } }
 
   validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes }
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 1268290bc..aea8919af 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -3,12 +3,12 @@
 #
 # Table name: domain_blocks
 #
+#  id           :integer          not null, primary key
 #  domain       :string           default(""), not null
 #  created_at   :datetime         not null
 #  updated_at   :datetime         not null
 #  severity     :integer          default("silence")
 #  reject_media :boolean          default(FALSE), not null
-#  id           :integer          not null, primary key
 #
 
 class DomainBlock < ApplicationRecord
diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb
index 839038bea..a104810d1 100644
--- a/app/models/email_domain_block.rb
+++ b/app/models/email_domain_block.rb
@@ -4,14 +4,33 @@
 # Table name: email_domain_blocks
 #
 #  id         :integer          not null, primary key
-#  domain     :string           not null
+#  domain     :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #
 
 class EmailDomainBlock < ApplicationRecord
+  before_validation :normalize_domain
+
+  validates :domain, presence: true, uniqueness: true
+
   def self.block?(email)
-    domain = email.gsub(/.+@([^.]+)/, '\1')
+    _, domain = email.split('@', 2)
+
+    return true if domain.nil?
+
+    begin
+      domain = TagManager.instance.normalize_domain(domain)
+    rescue Addressable::URI::InvalidURIError
+      return true
+    end
+
     where(domain: domain).exists?
   end
+
+  private
+
+  def normalize_domain
+    self.domain = TagManager.instance.normalize_domain(domain)
+  end
 end
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index d28d5c05b..c38838f2a 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -3,10 +3,10 @@
 #
 # Table name: favourites
 #
+#  id         :integer          not null, primary key
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #  account_id :integer          not null
-#  id         :integer          not null, primary key
 #  status_id  :integer          not null
 #
 
diff --git a/app/models/feed.rb b/app/models/feed.rb
index 5f7b7877a..d99f1ffb2 100644
--- a/app/models/feed.rb
+++ b/app/models/feed.rb
@@ -1,36 +1,27 @@
 # frozen_string_literal: true
 
 class Feed
-  def initialize(type, account)
-    @type    = type
-    @account = account
+  def initialize(type, id)
+    @type = type
+    @id   = id
   end
 
   def get(limit, max_id = nil, since_id = nil)
-    if redis.exists("account:#{@account.id}:regeneration")
-      from_database(limit, max_id, since_id)
-    else
-      from_redis(limit, max_id, since_id)
-    end
+    from_redis(limit, max_id, since_id)
   end
 
-  private
+  protected
 
   def from_redis(limit, max_id, since_id)
     max_id     = '+inf' if max_id.blank?
     since_id   = '-inf' if since_id.blank?
     unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
-    Status.where(id: unhydrated).cache_ids
-  end
 
-  def from_database(limit, max_id, since_id)
-    Status.as_home_timeline(@account)
-          .paginate_by_max_id(limit, max_id, since_id)
-          .reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
+    Status.where(id: unhydrated).cache_ids
   end
 
   def key
-    FeedManager.instance.key(@type, @account.id)
+    FeedManager.instance.key(@type, @id)
   end
 
   def redis
diff --git a/app/models/follow.rb b/app/models/follow.rb
index a8ddcb7f0..3fb665afc 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -3,10 +3,10 @@
 #
 # Table name: follows
 #
+#  id                :integer          not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
 #  account_id        :integer          not null
-#  id                :integer          not null, primary key
 #  target_account_id :integer          not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index 1a1c52382..ebf6959ce 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -3,10 +3,10 @@
 #
 # Table name: follow_requests
 #
+#  id                :integer          not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
 #  account_id        :integer          not null
-#  id                :integer          not null, primary key
 #  target_account_id :integer          not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #
@@ -28,7 +28,5 @@ class FollowRequest < ApplicationRecord
     destroy!
   end
 
-  def reject!
-    destroy!
-  end
+  alias reject! destroy!
 end
diff --git a/app/models/home_feed.rb b/app/models/home_feed.rb
new file mode 100644
index 000000000..b943a34ce
--- /dev/null
+++ b/app/models/home_feed.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class HomeFeed < Feed
+  def initialize(account)
+    @type    = :home
+    @id      = account.id
+    @account = account
+  end
+
+  def get(limit, max_id = nil, since_id = nil)
+    if redis.exists("account:#{@account.id}:regeneration")
+      from_database(limit, max_id, since_id)
+    else
+      super
+    end
+  end
+
+  private
+
+  def from_database(limit, max_id, since_id)
+    Status.as_home_timeline(@account)
+          .paginate_by_max_id(limit, max_id, since_id)
+          .reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
+  end
+end
diff --git a/app/models/import.rb b/app/models/import.rb
index 8ae7e3a46..091fb3044 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -3,6 +3,7 @@
 #
 # Table name: imports
 #
+#  id                :integer          not null, primary key
 #  type              :integer          not null
 #  approved          :boolean          default(FALSE), not null
 #  created_at        :datetime         not null
@@ -12,7 +13,6 @@
 #  data_file_size    :integer
 #  data_updated_at   :datetime
 #  account_id        :integer          not null
-#  id                :integer          not null, primary key
 #
 
 class Import < ApplicationRecord
diff --git a/app/models/list.rb b/app/models/list.rb
new file mode 100644
index 000000000..5d7ba0065
--- /dev/null
+++ b/app/models/list.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: lists
+#
+#  id         :integer          not null, primary key
+#  account_id :integer
+#  title      :string           default(""), not null
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+
+class List < ApplicationRecord
+  include Paginable
+
+  belongs_to :account
+
+  has_many :list_accounts, inverse_of: :list, dependent: :destroy
+  has_many :accounts, through: :list_accounts
+
+  validates :title, presence: true
+end
diff --git a/app/models/list_account.rb b/app/models/list_account.rb
new file mode 100644
index 000000000..c08239aa0
--- /dev/null
+++ b/app/models/list_account.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: list_accounts
+#
+#  id         :integer          not null, primary key
+#  list_id    :integer          not null
+#  account_id :integer          not null
+#  follow_id  :integer          not null
+#
+
+class ListAccount < ApplicationRecord
+  belongs_to :list, required: true
+  belongs_to :account, required: true
+  belongs_to :follow, required: true
+
+  before_validation :set_follow
+
+  private
+
+  def set_follow
+    self.follow = Follow.find_by(account_id: list.account_id, target_account_id: account.id)
+  end
+end
diff --git a/app/models/list_feed.rb b/app/models/list_feed.rb
new file mode 100644
index 000000000..f371e4ed9
--- /dev/null
+++ b/app/models/list_feed.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class ListFeed < Feed
+  def initialize(list)
+    @type    = :list
+    @id      = list.id
+  end
+end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index f6c8879c5..368ccef3a 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -10,12 +10,12 @@
 #  file_file_size    :integer
 #  file_updated_at   :datetime
 #  remote_url        :string           default(""), not null
-#  account_id        :integer
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
 #  shortcode         :string
 #  type              :integer          default("image"), not null
 #  file_meta         :json
+#  account_id        :integer
 #  description       :text
 #
 
diff --git a/app/models/mention.rb b/app/models/mention.rb
index 3700c781c..14533e6a9 100644
--- a/app/models/mention.rb
+++ b/app/models/mention.rb
@@ -3,11 +3,11 @@
 #
 # Table name: mentions
 #
+#  id         :integer          not null, primary key
 #  status_id  :integer
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #  account_id :integer
-#  id         :integer          not null, primary key
 #
 
 class Mention < ApplicationRecord
diff --git a/app/models/mute.rb b/app/models/mute.rb
index bcd3d247c..ca984641a 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -3,12 +3,12 @@
 #
 # Table name: mutes
 #
+#  id                 :integer          not null, primary key
 #  created_at         :datetime         not null
 #  updated_at         :datetime         not null
+#  hide_notifications :boolean          default(TRUE), not null
 #  account_id         :integer          not null
-#  id                 :integer          not null, primary key
 #  target_account_id  :integer          not null
-#  hide_notifications :boolean          default(TRUE), not null
 #
 
 class Mute < ApplicationRecord
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 0a5d987cf..a3ffb1f45 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -4,11 +4,11 @@
 # Table name: notifications
 #
 #  id              :integer          not null, primary key
-#  account_id      :integer
 #  activity_id     :integer
 #  activity_type   :string
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
+#  account_id      :integer
 #  from_account_id :integer
 #
 
diff --git a/app/models/report.rb b/app/models/report.rb
index bffb42b48..c36f8db0a 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -3,6 +3,7 @@
 #
 # Table name: reports
 #
+#  id                         :integer          not null, primary key
 #  status_ids                 :integer          default([]), not null, is an Array
 #  comment                    :text             default(""), not null
 #  action_taken               :boolean          default(FALSE), not null
@@ -10,7 +11,6 @@
 #  updated_at                 :datetime         not null
 #  account_id                 :integer          not null
 #  action_taken_by_account_id :integer
-#  id                         :integer          not null, primary key
 #  target_account_id          :integer          not null
 #
 
diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb
index c1645223b..d19489b36 100644
--- a/app/models/session_activation.rb
+++ b/app/models/session_activation.rb
@@ -4,24 +4,24 @@
 # Table name: session_activations
 #
 #  id                       :integer          not null, primary key
-#  user_id                  :integer          not null
 #  session_id               :string           not null
 #  created_at               :datetime         not null
 #  updated_at               :datetime         not null
 #  user_agent               :string           default(""), not null
 #  ip                       :inet
 #  access_token_id          :integer
+#  user_id                  :integer          not null
 #  web_push_subscription_id :integer
 #
 
-#  id              :integer          not null, primary key
-#  user_id         :integer          not null
+#  id              :bigint           not null, primary key
+#  user_id         :bigint           not null
 #  session_id      :string           not null
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
 #  user_agent      :string           default(""), not null
 #  ip              :inet
-#  access_token_id :integer
+#  access_token_id :bigint
 #
 
 class SessionActivation < ApplicationRecord
diff --git a/app/models/setting.rb b/app/models/setting.rb
index a14f156a1..df93590ce 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -3,12 +3,12 @@
 #
 # Table name: settings
 #
+#  id         :integer          not null, primary key
 #  var        :string           not null
 #  value      :text
 #  thing_type :string
 #  created_at :datetime
 #  updated_at :datetime
-#  id         :integer          not null, primary key
 #  thing_id   :integer
 #
 
diff --git a/app/models/status.rb b/app/models/status.rb
index d78a921b5..172d3a665 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -5,7 +5,6 @@
 #
 #  id                     :integer          not null, primary key
 #  uri                    :string
-#  account_id             :integer          not null
 #  text                   :text             default(""), not null
 #  created_at             :datetime         not null
 #  updated_at             :datetime         not null
@@ -14,8 +13,6 @@
 #  url                    :string
 #  sensitive              :boolean          default(FALSE), not null
 #  visibility             :integer          default("public"), not null
-#  in_reply_to_account_id :integer
-#  application_id         :integer
 #  spoiler_text           :text             default(""), not null
 #  reply                  :boolean          default(FALSE), not null
 #  favourites_count       :integer          default(0), not null
@@ -23,6 +20,9 @@
 #  language               :string
 #  conversation_id        :integer
 #  local                  :boolean
+#  account_id             :integer          not null
+#  application_id         :integer
+#  in_reply_to_account_id :integer
 #
 
 class Status < ApplicationRecord
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index 720cd518c..36fe487dc 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -3,13 +3,13 @@
 #
 # Table name: stream_entries
 #
+#  id            :integer          not null, primary key
 #  activity_id   :integer
 #  activity_type :string
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
 #  hidden        :boolean          default(FALSE), not null
 #  account_id    :integer
-#  id            :integer          not null, primary key
 #
 
 class StreamEntry < ApplicationRecord
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 39860196b..7f2eeab91 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -3,6 +3,7 @@
 #
 # Table name: subscriptions
 #
+#  id                          :integer          not null, primary key
 #  callback_url                :string           default(""), not null
 #  secret                      :string
 #  expires_at                  :datetime
@@ -12,7 +13,6 @@
 #  last_successful_delivery_at :datetime
 #  domain                      :string
 #  account_id                  :integer          not null
-#  id                          :integer          not null, primary key
 #
 
 class Subscription < ApplicationRecord
diff --git a/app/models/user.rb b/app/models/user.rb
index 325e27f44..b9b228c00 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,7 +5,6 @@
 #
 #  id                        :integer          not null, primary key
 #  email                     :string           default(""), not null
-#  account_id                :integer          not null
 #  created_at                :datetime         not null
 #  updated_at                :datetime         not null
 #  encrypted_password        :string           default(""), not null
@@ -31,10 +30,14 @@
 #  last_emailed_at           :datetime
 #  otp_backup_codes          :string           is an Array
 #  filtered_languages        :string           default([]), not null, is an Array
+#  account_id                :integer          not null
+#  disabled                  :boolean          default(FALSE), not null
+#  moderator                 :boolean          default(FALSE), not null
 #
 
 class User < ApplicationRecord
   include Settings::Extend
+
   ACTIVE_DURATION = 14.days
 
   devise :registerable, :recoverable,
@@ -51,8 +54,10 @@ class User < ApplicationRecord
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
   validates_with BlacklistedEmailValidator, if: :email_changed?
 
-  scope :recent,    -> { order(id: :desc) }
-  scope :admins,    -> { where(admin: true) }
+  scope :recent, -> { order(id: :desc) }
+  scope :admins, -> { where(admin: true) }
+  scope :moderators, -> { where(moderator: true) }
+  scope :staff, -> { admins.or(moderators) }
   scope :confirmed, -> { where.not(confirmed_at: nil) }
   scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
   scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) }
@@ -68,54 +73,71 @@ class User < ApplicationRecord
 
   has_many :session_activations, dependent: :destroy
 
+  delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
+           :reduce_motion, :system_font_ui, :noindex, :theme,
+           to: :settings, prefix: :setting, allow_nil: false
+
   def confirmed?
     confirmed_at.present?
   end
 
-  def disable_two_factor!
-    self.otp_required_for_login = false
-    otp_backup_codes&.clear
-    save!
-  end
-
-  def setting_default_privacy
-    settings.default_privacy || (account.locked? ? 'private' : 'public')
+  def staff?
+    admin? || moderator?
   end
 
-  def setting_default_sensitive
-    settings.default_sensitive
+  def role
+    if admin?
+      'admin'
+    elsif moderator?
+      'moderator'
+    else
+      'user'
+    end
   end
 
-  def setting_unfollow_modal
-    settings.unfollow_modal
+  def disable!
+    update!(disabled: true,
+            last_sign_in_at: current_sign_in_at,
+            current_sign_in_at: nil)
   end
 
-  def setting_boost_modal
-    settings.boost_modal
+  def enable!
+    update!(disabled: false)
   end
 
-  def setting_delete_modal
-    settings.delete_modal
+  def confirm!
+    skip_confirmation!
+    save!
   end
 
-  def setting_auto_play_gif
-    settings.auto_play_gif
+  def promote!
+    if moderator?
+      update!(moderator: false, admin: true)
+    elsif !admin?
+      update!(moderator: true)
+    end
   end
 
-  def setting_reduce_motion
-    settings.reduce_motion
+  def demote!
+    if admin?
+      update!(admin: false, moderator: true)
+    elsif moderator?
+      update!(moderator: false)
+    end
   end
 
-  def setting_system_font_ui
-    settings.system_font_ui
+  def disable_two_factor!
+    self.otp_required_for_login = false
+    otp_backup_codes&.clear
+    save!
   end
 
-  def setting_noindex
-    settings.noindex
+  def active_for_authentication?
+    super && !disabled?
   end
 
-  def setting_theme
-    settings.theme
+  def setting_default_privacy
+    settings.default_privacy || (account.locked? ? 'private' : 'public')
   end
 
   def token_for_app(a)
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index cb15dfa37..5aee92d27 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -24,12 +24,12 @@ class Web::PushSubscription < ApplicationRecord
   end
 
   def pushable?(notification)
-    data && data.key?('alerts') && data['alerts'][notification.type.to_s]
+    data&.key?('alerts') && data['alerts'][notification.type.to_s]
   end
 
   def as_payload
     payload = { id: id, endpoint: endpoint }
-    payload[:alerts] = data['alerts'] if data && data.key?('alerts')
+    payload[:alerts] = data['alerts'] if data&.key?('alerts')
     payload
   end
 
diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb
index 1b0bfb2b7..12b9d1226 100644
--- a/app/models/web/setting.rb
+++ b/app/models/web/setting.rb
@@ -3,10 +3,10 @@
 #
 # Table name: web_settings
 #
+#  id         :integer          not null, primary key
 #  data       :json
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  id         :integer          not null, primary key
 #  user_id    :integer
 #
 
diff --git a/app/policies/account_moderation_note_policy.rb b/app/policies/account_moderation_note_policy.rb
new file mode 100644
index 000000000..885411a5b
--- /dev/null
+++ b/app/policies/account_moderation_note_policy.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AccountModerationNotePolicy < ApplicationPolicy
+  def create?
+    staff?
+  end
+
+  def destroy?
+    admin? || owner?
+  end
+
+  private
+
+  def owner?
+    record.account_id == current_account&.id
+  end
+end
diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb
new file mode 100644
index 000000000..85e2c8419
--- /dev/null
+++ b/app/policies/account_policy.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class AccountPolicy < ApplicationPolicy
+  def index?
+    staff?
+  end
+
+  def show?
+    staff?
+  end
+
+  def suspend?
+    staff? && !record.user&.staff?
+  end
+
+  def unsuspend?
+    staff?
+  end
+
+  def silence?
+    staff? && !record.user&.staff?
+  end
+
+  def unsilence?
+    staff?
+  end
+
+  def redownload?
+    admin?
+  end
+
+  def subscribe?
+    admin?
+  end
+
+  def unsubscribe?
+    admin?
+  end
+
+  def memorialize?
+    admin? && !record.user&.admin?
+  end
+end
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
new file mode 100644
index 000000000..3e617001f
--- /dev/null
+++ b/app/policies/application_policy.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class ApplicationPolicy
+  attr_reader :current_account, :record
+
+  def initialize(current_account, record)
+    @current_account = current_account
+    @record          = record
+  end
+
+  delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true
+
+  private
+
+  def current_user
+    current_account&.user
+  end
+end
diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb
new file mode 100644
index 000000000..a8c3cbc73
--- /dev/null
+++ b/app/policies/custom_emoji_policy.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class CustomEmojiPolicy < ApplicationPolicy
+  def index?
+    staff?
+  end
+
+  def create?
+    admin?
+  end
+
+  def update?
+    admin?
+  end
+
+  def copy?
+    admin?
+  end
+
+  def enable?
+    staff?
+  end
+
+  def disable?
+    staff?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/policies/domain_block_policy.rb b/app/policies/domain_block_policy.rb
new file mode 100644
index 000000000..47c0a81af
--- /dev/null
+++ b/app/policies/domain_block_policy.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class DomainBlockPolicy < ApplicationPolicy
+  def index?
+    admin?
+  end
+
+  def show?
+    admin?
+  end
+
+  def create?
+    admin?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/policies/email_domain_block_policy.rb b/app/policies/email_domain_block_policy.rb
new file mode 100644
index 000000000..5a75ee183
--- /dev/null
+++ b/app/policies/email_domain_block_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class EmailDomainBlockPolicy < ApplicationPolicy
+  def index?
+    admin?
+  end
+
+  def create?
+    admin?
+  end
+
+  def destroy?
+    admin?
+  end
+end
diff --git a/app/policies/instance_policy.rb b/app/policies/instance_policy.rb
new file mode 100644
index 000000000..d1956e2de
--- /dev/null
+++ b/app/policies/instance_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class InstancePolicy < ApplicationPolicy
+  def index?
+    admin?
+  end
+
+  def resubscribe?
+    admin?
+  end
+end
diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb
new file mode 100644
index 000000000..95b5c30c8
--- /dev/null
+++ b/app/policies/report_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ReportPolicy < ApplicationPolicy
+  def update?
+    staff?
+  end
+
+  def index?
+    staff?
+  end
+
+  def show?
+    staff?
+  end
+end
diff --git a/app/policies/settings_policy.rb b/app/policies/settings_policy.rb
new file mode 100644
index 000000000..2dcb79f51
--- /dev/null
+++ b/app/policies/settings_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class SettingsPolicy < ApplicationPolicy
+  def update?
+    admin?
+  end
+
+  def show?
+    admin?
+  end
+end
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index f4a5e7c6c..369ede2b0 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -1,22 +1,19 @@
 # frozen_string_literal: true
 
-class StatusPolicy
-  attr_reader :account, :status
-
-  def initialize(account, status)
-    @account = account
-    @status = status
+class StatusPolicy < ApplicationPolicy
+  def index?
+    staff?
   end
 
   def show?
-    return false if local_only? && account.nil?
+    return false if local_only? && current_account.nil?
 
     if direct?
-      owned? || status.mentions.where(account: account).exists?
+      owned? || record.mentions.where(account: current_account).exists?
     elsif private?
-      owned? || account&.following?(status.account) || status.mentions.where(account: account).exists?
+      owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists?
     else
-      account.nil? || !status.account.blocking?(account)
+      current_account.nil? || !author.blocking?(current_account)
     end
   end
 
@@ -25,30 +22,34 @@ class StatusPolicy
   end
 
   def destroy?
-    admin? || owned?
+    staff? || owned?
   end
 
   alias unreblog? destroy?
 
-  private
-
-  def admin?
-    account&.user&.admin?
+  def update?
+    staff?
   end
 
+  private
+
   def direct?
-    status.direct_visibility?
+    record.direct_visibility?
   end
 
   def owned?
-    status.account.id == account&.id
+    author.id == current_account&.id
   end
 
   def private?
-    status.private_visibility?
+    record.private_visibility?
+  end
+
+  def author
+    record.account
   end
   
   def local_only?
-    status.local_only?
+    record.local_only?
   end
 end
diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb
new file mode 100644
index 000000000..ac9a8a6c4
--- /dev/null
+++ b/app/policies/subscription_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class SubscriptionPolicy < ApplicationPolicy
+  def index?
+    admin?
+  end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
new file mode 100644
index 000000000..aae207d06
--- /dev/null
+++ b/app/policies/user_policy.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+class UserPolicy < ApplicationPolicy
+  def reset_password?
+    staff? && !record.staff?
+  end
+
+  def disable_2fa?
+    admin? && !record.staff?
+  end
+
+  def confirm?
+    staff? && !record.confirmed?
+  end
+
+  def enable?
+    admin?
+  end
+
+  def disable?
+    admin? && !record.admin?
+  end
+
+  def promote?
+    admin? && promoteable?
+  end
+
+  def demote?
+    admin? && !record.admin? && demoteable?
+  end
+
+  private
+
+  def promoteable?
+    !record.staff? || !record.admin?
+  end
+
+  def demoteable?
+    record.staff?
+  end
+end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index 1f5ee789a..9dfa019f5 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -2,12 +2,17 @@
 
 class InitialStateSerializer < ActiveModel::Serializer
   attributes :meta, :compose, :accounts,
-             :media_attachments, :settings, :push_subscription
+             :media_attachments, :settings, :push_subscription,
+             :max_toot_chars
 
   has_many :custom_emojis, serializer: REST::CustomEmojiSerializer
 
+  def max_toot_chars
+    StatusLengthValidator::MAX_CHARS
+  end
+
   def custom_emojis
-    CustomEmoji.local
+    CustomEmoji.local.where(disabled: false)
   end
 
   def meta
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 2898011fd..abbacc374 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -4,7 +4,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
   include RoutingHelper
 
   attributes :uri, :title, :description, :email,
-             :version, :urls, :stats, :thumbnail
+             :version, :urls, :stats, :thumbnail, :max_toot_chars
 
   def uri
     Rails.configuration.x.local_domain
@@ -30,6 +30,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
     full_asset_url(instance_presenter.thumbnail.file.url) if instance_presenter.thumbnail
   end
 
+  def max_toot_chars
+    StatusLengthValidator::MAX_CHARS
+  end
+
   def stats
     {
       user_count: instance_presenter.user_count,
diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb
new file mode 100644
index 000000000..c0150888e
--- /dev/null
+++ b/app/serializers/rest/list_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class REST::ListSerializer < ActiveModel::Serializer
+  attributes :id, :title
+end
diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb
index e2a89a87c..8d7b7a17c 100644
--- a/app/services/activitypub/fetch_remote_status_service.rb
+++ b/app/services/activitypub/fetch_remote_status_service.rb
@@ -16,7 +16,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService
     return if actor_id.nil? || !trustworthy_attribution?(@json['id'], actor_id)
 
     actor = ActivityPub::TagManager.instance.uri_to_resource(actor_id, Account)
-    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil?
+    actor = ActivityPub::FetchRemoteAccountService.new.call(actor_id, id: true) if actor.nil? || needs_update(actor)
 
     return if actor.suspended?
 
@@ -44,4 +44,8 @@ class ActivityPub::FetchRemoteStatusService < BaseService
   def expected_type?
     %w(Note Article).include? @json['type']
   end
+
+  def needs_update(actor)
+    actor.possibly_stale?
+  end
 end
diff --git a/app/services/batched_remove_status_service.rb b/app/services/batched_remove_status_service.rb
index aa2229f13..21c775208 100644
--- a/app/services/batched_remove_status_service.rb
+++ b/app/services/batched_remove_status_service.rb
@@ -26,10 +26,11 @@ class BatchedRemoveStatusService < BaseService
     statuses.each(&:destroy)
 
     # Batch by source account
-    statuses.group_by(&:account_id).each do |_, account_statuses|
+    statuses.group_by(&:account_id).each_value do |account_statuses|
       account = account_statuses.first.account
 
       unpush_from_home_timelines(account, account_statuses)
+      unpush_from_list_timelines(account, account_statuses)
 
       if account.local?
         batch_stream_entries(account, account_statuses)
@@ -80,7 +81,15 @@ class BatchedRemoveStatusService < BaseService
 
     recipients.each do |follower|
       statuses.each do |status|
-        FeedManager.instance.unpush(:home, follower, status)
+        FeedManager.instance.unpush_from_home(follower, status)
+      end
+    end
+  end
+
+  def unpush_from_list_timelines(account, statuses)
+    account.lists.select(:id, :account_id).each do |list|
+      statuses.each do |status|
+        FeedManager.instance.unpush_from_list(list, status)
       end
     end
   end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 2214d73dd..0f77556dc 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -17,6 +17,7 @@ class FanOutOnWriteService < BaseService
       deliver_to_direct_timelines(status)
     else
       deliver_to_followers(status)
+      deliver_to_lists(status)
     end
 
     return if status.account.silenced? || !status.public_visibility? || status.reblog?
@@ -32,7 +33,7 @@ class FanOutOnWriteService < BaseService
 
   def deliver_to_self(status)
     Rails.logger.debug "Delivering status #{status.id} to author"
-    FeedManager.instance.push(:home, status.account, status)
+    FeedManager.instance.push_to_home(status.account, status)
   end
 
   def deliver_to_followers(status)
@@ -40,7 +41,17 @@ class FanOutOnWriteService < BaseService
 
     status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |followers|
       FeedInsertWorker.push_bulk(followers) do |follower|
-        [status.id, follower.id]
+        [status.id, follower.id, :home]
+      end
+    end
+  end
+
+  def deliver_to_lists(status)
+    Rails.logger.debug "Delivering status #{status.id} to lists"
+
+    status.account.lists.joins(account: :user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_in_batches do |lists|
+      FeedInsertWorker.push_bulk(lists) do |list|
+        [status.id, list.id, :list]
       end
     end
   end
@@ -51,7 +62,7 @@ class FanOutOnWriteService < BaseService
     status.mentions.includes(:account).each do |mention|
       mentioned_account = mention.account
       next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mention.account_id)
-      FeedManager.instance.push(:home, mentioned_account, status)
+      FeedManager.instance.push_to_home(mentioned_account, status)
     end
   end
 
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 3fa3f152c..d5960c3ad 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -36,17 +36,58 @@ class NotifyService < BaseService
     false
   end
 
+  def following_sender?
+    return @following_sender if defined?(@following_sender)
+    @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
+  end
+
+  def optional_non_follower?
+    @recipient.user.settings.interactions['must_be_follower']  && !@notification.from_account.following?(@recipient)
+  end
+
+  def optional_non_following?
+    @recipient.user.settings.interactions['must_be_following'] && !following_sender?
+  end
+
+  def direct_message?
+    @notification.type == :mention && @notification.target_status.direct_visibility?
+  end
+
+  def response_to_recipient?
+    @notification.target_status.in_reply_to_account_id == @recipient.id
+  end
+
+  def optional_non_following_and_direct?
+    direct_message? &&
+      @recipient.user.settings.interactions['must_be_following_dm'] &&
+      !following_sender? &&
+      !response_to_recipient?
+  end
+
+  def hellbanned?
+    @notification.from_account.silenced? && !following_sender?
+  end
+
+  def from_self?
+    @recipient.id == @notification.from_account.id
+  end
+
+  def domain_blocking?
+    @recipient.domain_blocking?(@notification.from_account.domain) && !following_sender?
+  end
+
   def blocked?
-    blocked   = @recipient.suspended?                                                                                                # Skip if the recipient account is suspended anyway
-    blocked ||= @recipient.id == @notification.from_account.id                                                                       # Skip for interactions with self
-    blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) && !@recipient.following?(@notification.from_account) # Skip for domain blocked accounts
-    blocked ||= @recipient.blocking?(@notification.from_account)                                                                     # Skip for blocked accounts
+    blocked   = @recipient.suspended?                            # Skip if the recipient account is suspended anyway
+    blocked ||= from_self?                                       # Skip for interactions with self
+    blocked ||= domain_blocking?                                 # Skip for domain blocked accounts
+    blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
     blocked ||= @recipient.muting_notifications?(@notification.from_account)
-    blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account))                         # Hellban
-    blocked ||= (@recipient.user.settings.interactions['must_be_follower']  && !@notification.from_account.following?(@recipient))   # Options
-    blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account))   # Options
+    blocked ||= hellbanned?                                      # Hellban
+    blocked ||= optional_non_follower?                           # Options
+    blocked ||= optional_non_following?                          # Options
+    blocked ||= optional_non_following_and_direct?               # Options
     blocked ||= conversation_muted?
-    blocked ||= send("blocked_#{@notification.type}?")                                                                               # Type-dependent filters
+    blocked ||= send("blocked_#{@notification.type}?")           # Type-dependent filters
     blocked
   end
 
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index d1b8f42c7..974c586f2 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -74,11 +74,11 @@ class PostStatusService < BaseService
   end
 
   def process_mentions_service
-    @process_mentions_service ||= ProcessMentionsService.new
+    ProcessMentionsService.new
   end
 
   def process_hashtags_service
-    @process_hashtags_service ||= ProcessHashtagsService.new
+    ProcessHashtagsService.new
   end
 
   def redis
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index 1c3eea369..a229d4ff8 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -10,23 +10,26 @@ class ProcessMentionsService < BaseService
   def call(status)
     return unless status.local?
 
-    status.text.scan(Account::MENTION_RE).each do |match|
-      username, domain  = match.first.split('@')
-      mentioned_account = Account.find_remote(username, domain)
-
-      if mentioned_account.nil? && !domain.nil?
-        begin
-          mentioned_account = follow_remote_account_service.call(match.first.to_s)
-        rescue Goldfinger::Error, HTTP::Error
-          mentioned_account = nil
-        end
+    status.text = status.text.gsub(Account::MENTION_RE) do |match|
+      begin
+        mentioned_account = resolve_remote_account_service.call($1)
+      rescue Goldfinger::Error, HTTP::Error
+        mentioned_account = nil
       end
 
-      next if mentioned_account.nil?
+      if mentioned_account.nil?
+        username, domain  = match.first.split('@')
+        mentioned_account = Account.find_remote(username, domain)
+      end
+
+      next match if mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && status.stream_entry.hidden?)
 
       mentioned_account.mentions.where(status: status).first_or_create(status: status)
+      "@#{mentioned_account.acct}"
     end
 
+    status.save!
+
     status.mentions.includes(:account).each do |mention|
       create_notification(status, mention)
     end
@@ -54,7 +57,7 @@ class ProcessMentionsService < BaseService
     ).as_json).sign!(status.account))
   end
 
-  def follow_remote_account_service
-    @follow_remote_account_service ||= ResolveRemoteAccountService.new
+  def resolve_remote_account_service
+    ResolveRemoteAccountService.new
   end
 end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 8eef3e57e..9617081fd 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -14,6 +14,7 @@ class RemoveStatusService < BaseService
 
     remove_from_self if status.account.local?
     remove_from_followers
+    remove_from_lists
     remove_from_affected
     remove_reblogs
     remove_from_hashtags
@@ -31,12 +32,18 @@ class RemoveStatusService < BaseService
   private
 
   def remove_from_self
-    unpush(:home, @account, @status)
+    FeedManager.instance.unpush_from_home(@account, @status)
   end
 
   def remove_from_followers
     @account.followers.local.find_each do |follower|
-      unpush(:home, follower, @status)
+      FeedManager.instance.unpush_from_home(follower, @status)
+    end
+  end
+
+  def remove_from_lists
+    @account.lists.select(:id, :account_id).find_each do |list|
+      FeedManager.instance.unpush_from_list(list, @status)
     end
   end
 
@@ -102,10 +109,6 @@ class RemoveStatusService < BaseService
     end
   end
 
-  def unpush(type, receiver, status)
-    FeedManager.instance.unpush(type, receiver, status)
-  end
-
   def remove_from_hashtags
     return unless @status.public_visibility?
 
diff --git a/app/services/resolve_remote_account_service.rb b/app/services/resolve_remote_account_service.rb
index 3d0a36f6c..3293fe40f 100644
--- a/app/services/resolve_remote_account_service.rb
+++ b/app/services/resolve_remote_account_service.rb
@@ -124,11 +124,11 @@ class ResolveRemoteAccountService < BaseService
   end
 
   def auto_suspend?
-    domain_block && domain_block.suspend?
+    domain_block&.suspend?
   end
 
   def auto_silence?
-    domain_block && domain_block.silence?
+    domain_block&.silence?
   end
 
   def domain_block
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 983c5495b..5b37ba9ba 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -1,22 +1,27 @@
 # frozen_string_literal: true
 
 class SuspendAccountService < BaseService
-  def call(account, remove_user = false)
+  def call(account, options = {})
     @account = account
+    @options = options
 
-    purge_user if remove_user
-    purge_profile
-    purge_content
-    unsubscribe_push_subscribers
+    purge_user!
+    purge_profile!
+    purge_content!
+    unsubscribe_push_subscribers!
   end
 
   private
 
-  def purge_user
-    @account.user.destroy
+  def purge_user!
+    if @options[:remove_user]
+      @account.user&.destroy
+    else
+      @account.user&.disable!
+    end
   end
 
-  def purge_content
+  def purge_content!
     @account.statuses.reorder(nil).find_in_batches do |statuses|
       BatchedRemoveStatusService.new.call(statuses)
     end
@@ -33,7 +38,7 @@ class SuspendAccountService < BaseService
     end
   end
 
-  def purge_profile
+  def purge_profile!
     @account.suspended    = true
     @account.display_name = ''
     @account.note         = ''
@@ -42,7 +47,7 @@ class SuspendAccountService < BaseService
     @account.save!
   end
 
-  def unsubscribe_push_subscribers
+  def unsubscribe_push_subscribers!
     destroy_all(@account.subscriptions)
   end
 
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index 2ce5d1ee9..79d17742a 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class StatusLengthValidator < ActiveModel::Validator
-  MAX_CHARS = 512
+  MAX_CHARS = (ENV['MAX_TOOT_CHARS'] || 500).to_i
 
   def validate(status)
     return unless status.local? && !status.reblog?
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index 5b504912d..94ec5ae5b 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -1,22 +1,23 @@
 - processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account
 .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
   .card__illustration
-    - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
-      .controls
-        - if current_account.following?(account)
-          = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
-            = fa_icon 'user-times'
-            = t('accounts.unfollow')
-        - else
-          = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
-            = fa_icon 'user-plus'
-            = t('accounts.follow')
-    - elsif !user_signed_in?
-      .controls
-        .remote-follow
-          = link_to account_remote_follow_path(account), class: 'icon-button' do
-            = fa_icon 'user-plus'
-            = t('accounts.remote_follow')
+    - unless account.memorial?
+      - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account)
+        .controls
+          - if current_account.following?(account)
+            = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do
+              = fa_icon 'user-times'
+              = t('accounts.unfollow')
+          - else
+            = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do
+              = fa_icon 'user-plus'
+              = t('accounts.follow')
+      - elsif !user_signed_in?
+        .controls
+          .remote-follow
+            = link_to account_remote_follow_path(account), class: 'icon-button' do
+              = fa_icon 'user-plus'
+              = t('accounts.remote_follow')
 
     .avatar= image_tag account.avatar.url(:original), class: 'u-photo'
 
@@ -29,8 +30,12 @@
 
     - if account.user_admin?
       .roles
-        .account-role
+        .account-role.admin
           = t 'accounts.roles.admin'
+    - elsif account.user_moderator?
+      .roles
+        .account-role.moderator
+          = t 'accounts.roles.moderator'
     .bio
       .account__header__content.p-note.emojify!=processed_bio[:text]
       - if processed_bio[:metadata].length > 0
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 6c90b2c04..fd8ad5530 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -12,7 +12,9 @@
   = opengraph 'og:type', 'profile'
   = render 'og', account: @account, url: short_account_url(@account, only_path: false)
 
-- if show_landing_strip?
+- if @account.memorial?
+  .memoriam-strip= t('in_memoriam_html')
+- elsif show_landing_strip?
   = render partial: 'shared/landing_strip', locals: { account: @account }
 
 .h-feed
diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml
index 4651630e9..6761a4319 100644
--- a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml
+++ b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml
@@ -7,4 +7,4 @@
     %time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) }
       = l account_moderation_note.created_at
   %td
-    = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete
+    = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note)
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index 1b56a3a31..27a0682d8 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -42,7 +42,7 @@
       - if params[key].present?
         = hidden_field_tag key, params[key]
 
-    - %i(username display_name email ip).each do |key|
+    - %i(username by_domain display_name email ip).each do |key|
       .input.string.optional
         = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}")
 
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 1f5c8fcf5..ddb1cf15d 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -16,8 +16,27 @@
 
       - if @account.local?
         %tr
+          %th= t('admin.accounts.role')
+          %td
+            = t("admin.accounts.roles.#{@account.user&.role}")
+            = table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user)
+            = table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
+        %tr
           %th= t('admin.accounts.email')
-          %td= @account.user_email
+          %td
+            = @account.user_email
+
+            - if @account.user_confirmed?
+              = fa_icon('check')
+        %tr
+          %th= t('admin.accounts.login_status')
+          %td
+            - if @account.user&.disabled?
+              = t('admin.accounts.disabled')
+              = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
+            - else
+              = t('admin.accounts.enabled')
+              = table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post if can?(:disable, @account.user)
         %tr
           %th= t('admin.accounts.most_recent_ip')
           %td= @account.user_current_sign_in_ip
@@ -62,26 +81,28 @@
 %div{ style: 'overflow: hidden' }
   %div{ style: 'float: right' }
     - if @account.local?
-      = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
+      = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
       - if @account.user&.otp_required_for_login?
-        = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button'
+        = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
+      - unless @account.memorial?
+        = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:memorialize, @account)
     - else
-      = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button'
+      = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
 
   %div{ style: 'float: left' }
     - if @account.silenced?
-      = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
+      = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account)
     - else
-      = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
+      = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' if can?(:silence, @account)
 
     - if @account.local?
       - unless @account.user_confirmed?
-        = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button'
+        = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
 
     - if @account.suspended?
-      = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
+      = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account)
     - else
-      = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+      = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account)
 
 - unless @account.local?
   %hr
@@ -107,9 +128,9 @@
 
   %div{ style: 'overflow: hidden' }
     %div{ style: 'float: right' }
-      = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button'
+      = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' if can?(:subscribe, @account)
       - if @account.subscribed?
-        = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative'
+        = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
 
   %hr
   %h3 ActivityPub
diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml
index 399d13bbd..bab34bc8d 100644
--- a/app/views/admin/custom_emojis/_custom_emoji.html.haml
+++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml
@@ -1,6 +1,6 @@
 %tr
   %td
-    = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:"
+    = custom_emoji_tag(custom_emoji)
   %td
     %samp= ":#{custom_emoji.shortcode}:"
   %td
@@ -15,7 +15,10 @@
       - else
         = table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }), method: :patch
     - else
-      = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post
+      - if custom_emoji.local_counterpart.present?
+        = link_to safe_join([custom_emoji_tag(custom_emoji.local_counterpart), t('admin.custom_emojis.overwrite')]), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post, class: 'table-action-link'
+      - else
+        = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post
   %td
     - if custom_emoji.disabled?
       = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index cba2bbbd4..63b3a0c26 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1,11 +1,7 @@
 - content_for :header_tags do
-  %link{ href: asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
-  %link{ href: asset_pack_path('features/direct_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
+  - if theme_data['preload']
+    - theme_data['preload'].each do |link|
+      %link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
   %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
   %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
 
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index b1fd9ef40..24b74c787 100755
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -29,7 +29,6 @@
     = yield :header_tags
 
   - body_classes ||= @body_classes || ''
-  - body_classes += ' reduce-motion' if current_account&.user&.setting_reduce_motion
   - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui
 
   %body{ class: add_rtl_body_class(body_classes) }
diff --git a/app/views/settings/applications/new.html.haml b/app/views/settings/applications/new.html.haml
index 5274a430c..aa2281fea 100644
--- a/app/views/settings/applications/new.html.haml
+++ b/app/views/settings/applications/new.html.haml
@@ -3,6 +3,6 @@
 
 = simple_form_for @application, url: settings_applications_path do |f|
   = render 'fields', f: f
-  
+
   .actions
     = f.button :button, t('doorkeeper.applications.buttons.submit'), type: :submit
diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml
index 12baed088..390682d6f 100644
--- a/app/views/settings/applications/show.html.haml
+++ b/app/views/settings/applications/show.html.haml
@@ -25,7 +25,7 @@
 
 = simple_form_for @application, url: settings_application_path(@application), method: :put do |f|
   = render 'fields', f: f
-    
+
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
 
diff --git a/app/views/settings/notifications/show.html.haml b/app/views/settings/notifications/show.html.haml
index 80cd615c7..b718b62df 100644
--- a/app/views/settings/notifications/show.html.haml
+++ b/app/views/settings/notifications/show.html.haml
@@ -11,7 +11,7 @@
       = ff.input :reblog, as: :boolean, wrapper: :with_label
       = ff.input :favourite, as: :boolean, wrapper: :with_label
       = ff.input :mention, as: :boolean, wrapper: :with_label
- 
+
   .fields-group
     = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff|
       = ff.input :digest, as: :boolean, wrapper: :with_label
@@ -20,6 +20,7 @@
     = f.simple_fields_for :interactions, hash_to_object(current_user.settings.interactions) do |ff|
       = ff.input :must_be_follower, as: :boolean, wrapper: :with_label
       = ff.input :must_be_following, as: :boolean, wrapper: :with_label
+      = ff.input :must_be_following_dm, as: :boolean, wrapper: :with_label
 
   .actions
     = f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb
index 80edcfda7..0be16d994 100644
--- a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb
@@ -1,6 +1,6 @@
 <p>Boas vindas, <%= @resource.email %>!</p>
 
-<p>Você acabou de criar uma conta no <%= @instance %>.</p>
+<p>Você acabou de criar uma conta na instância <%= @instance %>.</p>
 
 <p>Para confirmar o seu cadastro, por favor clique no link a seguir: <br>
 <%= link_to 'Confirmar cadastro', confirmation_url(@resource, confirmation_token: @token) %>
@@ -9,4 +9,4 @@
 
 <p>Atenciosamente,<p>
 
-<p>A equipe do <%= @instance %></p>
+<p>A equipe da instância <%= @instance %></p>
diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb
index 95efb3436..578f7acb5 100644
--- a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb
@@ -1,6 +1,6 @@
 Boas vindas, <%= @resource.email %>!
 
-Você acabou de criar uma conta no <%= @instance %>.
+Você acabou de criar uma conta na instância <%= @instance %>.
 
 Para confirmar o seu cadastro, por favor clique no link a seguir:
 <%= confirmation_url(@resource, confirmation_token: @token) %>
@@ -9,4 +9,4 @@ Por favor, leia também os nossos termos e condições de uso <%= terms_url %>
 
 Atenciosamente,
 
-A equipe do <%= @instance %>
+A equipe da instância <%= @instance %>
diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
index de2f8b6e0..8a676498a 100644
--- a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb
@@ -1,10 +1,13 @@
-<p><%= @resource.email %> ,嗨呀!</p>
+<p><%= @resource.email %>,你好呀!</p>
 
-<p>你刚刚在 <%= @instance %> 创建了帐号。</p>
+<p>你刚刚在 <%= @instance %> 创建了一个帐户呢。</p>
 
-<p>点击下面的链接来完成注册啦 : <br>
+<p>点击下面的链接来完成注册啦:<br>
 <%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %>
 
-<p>别忘了看看 <%= link_to '使用条款', terms_url %>。</p>
+<p>上面的链接按不动?把下面的链接复制到地址栏再试试:<br>
+<span><%= confirmation_url(@resource, confirmation_token: @token) %></span>
 
-<p> <%= @instance %> 敬上</p>
\ No newline at end of file
+<p>记得读一读我们的<%= link_to '使用条款', terms_url %>哦。</p>
+
+<p>来自 <%= @instance %> 管理团队</p>
diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
index d7d4b4b23..25d901f16 100644
--- a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb
@@ -1,10 +1,10 @@
-<%= @resource.email %> ,嗨呀!
+<%= @resource.email %>,你好呀!
 
-你刚刚在 <%= @instance %> 创建了帐号。
+你刚刚在 <%= @instance %> 创建了一个帐户呢。
 
-点击下面的链接来完成注册啦 : <br>
-<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %>
+点击下面的链接来完成注册啦:
+<%= confirmation_url(@resource, confirmation_token: @token) %>
 
-别忘了看看 <%= link_to 'terms and conditions', terms_url %>。
+记得读一读我们的使用条款哦:<%= terms_url %>
 
-<%= @instance %> 敬上
\ No newline at end of file
+来自 <%= @instance %> 管理团队
\ No newline at end of file
diff --git a/app/views/user_mailer/password_change.pt-BR.html.erb b/app/views/user_mailer/password_change.pt-BR.html.erb
index 5f707ba09..a1aaa265e 100644
--- a/app/views/user_mailer/password_change.pt-BR.html.erb
+++ b/app/views/user_mailer/password_change.pt-BR.html.erb
@@ -1,3 +1,3 @@
 <p>Olá, <%= @resource.email %>!</p>
 
-<p>Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.</p>
+<p>Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada.</p>
diff --git a/app/views/user_mailer/password_change.pt-BR.text.erb b/app/views/user_mailer/password_change.pt-BR.text.erb
index d8b76648c..eb7368ba9 100644
--- a/app/views/user_mailer/password_change.pt-BR.text.erb
+++ b/app/views/user_mailer/password_change.pt-BR.text.erb
@@ -1,3 +1,3 @@
 Olá, <%= @resource.email %>!
 
-Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.
+Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada.
diff --git a/app/views/user_mailer/password_change.zh-cn.html.erb b/app/views/user_mailer/password_change.zh-cn.html.erb
index 115030af4..64e8b6b2f 100644
--- a/app/views/user_mailer/password_change.zh-cn.html.erb
+++ b/app/views/user_mailer/password_change.zh-cn.html.erb
@@ -1,3 +1,3 @@
-<p><%= @resource.email %>,嗨呀!</p>
+<p><%= @resource.email %>,你好呀!</p>
 
-<p>这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_</p>
+<p>提醒一下,你在 <%= @instance %> 上的密码被更改了哦。</p>
diff --git a/app/views/user_mailer/password_change.zh-cn.text.erb b/app/views/user_mailer/password_change.zh-cn.text.erb
index 5a989d324..dbc065173 100644
--- a/app/views/user_mailer/password_change.zh-cn.text.erb
+++ b/app/views/user_mailer/password_change.zh-cn.text.erb
@@ -1,3 +1,3 @@
-<%= @resource.email %>,嗨呀!
+<%= @resource.email %>,你好呀!
 
-这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_
+提醒一下,你在 <%= @instance %> 上的密码被更改了哦。
diff --git a/app/views/user_mailer/reset_password_instructions.oc.html.erb b/app/views/user_mailer/reset_password_instructions.oc.html.erb
index 6c775b3a1..92e4b8f8b 100644
--- a/app/views/user_mailer/reset_password_instructions.oc.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.oc.html.erb
@@ -1,6 +1,6 @@
 <p>Bonjorn <%= @resource.email %> !</p>
 
-<p>Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
+<p>Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.</p>
 
 <p><%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %></p>
 
diff --git a/app/views/user_mailer/reset_password_instructions.oc.text.erb b/app/views/user_mailer/reset_password_instructions.oc.text.erb
index 26432d2df..5a5219589 100644
--- a/app/views/user_mailer/reset_password_instructions.oc.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.oc.text.erb
@@ -1,6 +1,6 @@
 Bonjorn <%= @resource.email %> !
 
-Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.</p>
+Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.</p>
 
 <%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %>
 
diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb
index 940438b7c..9b21aae92 100644
--- a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb
@@ -1,8 +1,8 @@
 <p>Olá, <%= @resource.email %>!</p>
 
-<p>Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:</p>
+<p>Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo:</p>
 
 <p><%= link_to 'Mudar a minha senha', edit_password_url(@resource, reset_password_token: @token) %></p>
 
 <p>Se você não solicitou isso, por favor ignore este e-mail.</p>
-<p>A senha senha não será modificada até que você acesse o link acima e crie uma nova.</p>
+<p>A senha não será modificada até que você acesse o link acima e crie uma nova.</p>
diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb
index f574fe08f..2abff0c0d 100644
--- a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb
@@ -1,8 +1,8 @@
 Olá, <%= @resource.email %>!
 
-Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:
+Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo:
 
 <%= edit_password_url(@resource, reset_password_token: @token) %>
 
 Se você não solicitou isso, por favor ignore este e-mail.
-A senha senha não será modificada até que você acesse o link acima e crie uma nova.
+A senha não será modificada até que você acesse o link acima e crie uma nova.
diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
index 51e3073f1..124305675 100644
--- a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
+++ b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb
@@ -1,7 +1,8 @@
-<p><%= @resource.email %> ,嗨呀!!</p>
+<p><%= @resource.email %>,你好呀!</p>
 
-<p>有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:</p>
+<p>有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧:</p>
 
-<p><%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %></p>
+<p><%= link_to '修改密码', edit_password_url(@resource, reset_password_token: @token) %></p>
 
-<p>如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。</p>
+<p>如果你不想修改密码的话,还请忽略这封邮件哦。</p>
+<p>在你点击上面的链接并修改密码前,你的密码是不会改变的。</p>
diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
index 7df590f78..f7cd88847 100644
--- a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
+++ b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb
@@ -1,7 +1,8 @@
-<%= @resource.email %> ,嗨呀!!
+<%= @resource.email %>,你好呀!
 
-有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:
+有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧:
 
-<%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %>
+<%= edit_password_url(@resource, reset_password_token: @token) %>
 
-如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。
+如果你不想修改密码的话,还请忽略这封邮件哦。
+在你点击上面的链接并修改密码前,你的密码是不会改变的。
diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb
index 6338b1130..e41465ccc 100644
--- a/app/workers/admin/suspension_worker.rb
+++ b/app/workers/admin/suspension_worker.rb
@@ -6,6 +6,6 @@ class Admin::SuspensionWorker
   sidekiq_options queue: 'pull'
 
   def perform(account_id, remove_user = false)
-    SuspendAccountService.new.call(Account.find(account_id), remove_user)
+    SuspendAccountService.new.call(Account.find(account_id), remove_user: remove_user)
   end
 end
diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb
index 65c02d3ef..1ae3c877b 100644
--- a/app/workers/feed_insert_worker.rb
+++ b/app/workers/feed_insert_worker.rb
@@ -3,34 +3,41 @@
 class FeedInsertWorker
   include Sidekiq::Worker
 
-  attr_reader :status, :follower
-
-  def perform(status_id, follower_id)
-    @status = Status.find_by(id: status_id)
-    @follower = Account.find_by(id: follower_id)
+  def perform(status_id, id, type = :home)
+    @type     = type.to_sym
+    @status   = Status.find(status_id)
+
+    case @type
+    when :home
+      @follower = Account.find(id)
+    when :list
+      @list     = List.find(id)
+      @follower = @list.account
+    end
 
     check_and_insert
+  rescue ActiveRecord::RecordNotFound
+    true
   end
 
   private
 
   def check_and_insert
-    if records_available?
-      perform_push unless feed_filtered?
-    else
-      true
-    end
-  end
-
-  def records_available?
-    status.present? && follower.present?
+    perform_push unless feed_filtered?
   end
 
   def feed_filtered?
-    FeedManager.instance.filter?(:home, status, follower.id)
+    # Note: Lists are a variation of home, so the filtering rules
+    # of home apply to both
+    FeedManager.instance.filter?(:home, @status, @follower.id)
   end
 
   def perform_push
-    FeedManager.instance.push(:home, follower, status)
+    case @type
+    when :home
+      FeedManager.instance.push_to_home(@follower, @status)
+    when :list
+      FeedManager.instance.push_to_list(@list, @status)
+    end
   end
 end
diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb
index 697cbd6a6..d76d73d96 100644
--- a/app/workers/push_update_worker.rb
+++ b/app/workers/push_update_worker.rb
@@ -3,12 +3,13 @@
 class PushUpdateWorker
   include Sidekiq::Worker
 
-  def perform(account_id, status_id)
-    account = Account.find(account_id)
-    status  = Status.find(status_id)
-    message = InlineRenderer.render(status, account, :status)
+  def perform(account_id, status_id, timeline_id = nil)
+    account     = Account.find(account_id)
+    status      = Status.find(status_id)
+    message     = InlineRenderer.render(status, account, :status)
+    timeline_id = "timeline:#{account.id}" if timeline_id.nil?
 
-    Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
+    Redis.current.publish(timeline_id, Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
   rescue ActiveRecord::RecordNotFound
     true
   end
diff --git a/app/workers/thread_resolve_worker.rb b/app/workers/thread_resolve_worker.rb
index 38287e8e6..c18a778d5 100644
--- a/app/workers/thread_resolve_worker.rb
+++ b/app/workers/thread_resolve_worker.rb
@@ -3,7 +3,11 @@
 class ThreadResolveWorker
   include Sidekiq::Worker
 
-  sidekiq_options queue: 'pull', retry: false
+  sidekiq_options queue: 'pull', retry: 3
+
+  sidekiq_retry_in do |count|
+    15 + 10 * (count**4) + rand(10 * (count**4))
+  end
 
   def perform(child_status_id, parent_url)
     child_status  = Status.find(child_status_id)
diff --git a/bin/webpack b/bin/webpack
index 528233a78..9d3800c74 100755
--- a/bin/webpack
+++ b/bin/webpack
@@ -1,27 +1,17 @@
 #!/usr/bin/env ruby
-$stdout.sync = true
+# frozen_string_literal: true
+#
+# This file was generated by Bundler.
+#
+# The application 'webpack' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
 
-require "shellwords"
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+  Pathname.new(__FILE__).realpath)
 
-ENV["RAILS_ENV"] ||= "development"
-RAILS_ENV = ENV["RAILS_ENV"]
+require "rubygems"
+require "bundler/setup"
 
-ENV["NODE_ENV"] ||= RAILS_ENV
-NODE_ENV = ENV["NODE_ENV"]
-
-APP_PATH          = File.expand_path("../", __dir__)
-NODE_MODULES_PATH = File.join(APP_PATH, "node_modules")
-WEBPACK_CONFIG    = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js")
-
-unless File.exist?(WEBPACK_CONFIG)
-  puts "Webpack configuration not found."
-  puts "Please run bundle exec rails webpacker:install to install webpacker"
-  exit!
-end
-
-env = { "NODE_PATH" => NODE_MODULES_PATH.shellescape }
-cmd = [ "#{NODE_MODULES_PATH}/.bin/webpack", "--config", WEBPACK_CONFIG ] + ARGV
-
-Dir.chdir(APP_PATH) do
-  exec env, *cmd
-end
+load Gem.bin_path("webpacker", "webpack")
diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server
index c9672f663..cf701102a 100755
--- a/bin/webpack-dev-server
+++ b/bin/webpack-dev-server
@@ -1,68 +1,17 @@
 #!/usr/bin/env ruby
-$stdout.sync = true
-
-require "shellwords"
-require "yaml"
-require "socket"
-
-ENV["RAILS_ENV"] ||= "development"
-RAILS_ENV = ENV["RAILS_ENV"]
-
-ENV["NODE_ENV"] ||= RAILS_ENV
-NODE_ENV = ENV["NODE_ENV"]
-
-APP_PATH          = File.expand_path("../", __dir__)
-CONFIG_FILE       = File.join(APP_PATH, "config/webpacker.yml")
-NODE_MODULES_PATH = File.join(APP_PATH, "node_modules")
-WEBPACK_CONFIG    = File.join(APP_PATH, "config/webpack/#{NODE_ENV}.js")
-
-DEFAULT_LISTEN_HOST_ADDR = NODE_ENV == 'development' ? 'localhost' : '0.0.0.0'
-
-def args(key)
-  index = ARGV.index(key)
-  index ? ARGV[index + 1] : nil
-end
-
-begin
-  dev_server = YAML.load_file(CONFIG_FILE)[RAILS_ENV]["dev_server"]
-
-  HOSTNAME          = args('--host') || dev_server["host"]
-  PORT              = args('--port') || dev_server["port"]
-  HTTPS             = ARGV.include?('--https') || dev_server["https"]
-  DEV_SERVER_ADDR   = "http#{"s" if HTTPS}://#{HOSTNAME}:#{PORT}"
-  LISTEN_HOST_ADDR  = args('--listen-host') || DEFAULT_LISTEN_HOST_ADDR
-
-rescue Errno::ENOENT, NoMethodError
-  $stdout.puts "Webpack dev_server configuration not found in #{CONFIG_FILE}."
-  $stdout.puts "Please run bundle exec rails webpacker:install to install webpacker"
-  exit!
-end
-
-begin
-  server = TCPServer.new(LISTEN_HOST_ADDR, PORT)
-  server.close
-
-rescue Errno::EADDRINUSE
-  $stdout.puts "Another program is running on port #{PORT}. Set a new port in #{CONFIG_FILE} for dev_server"
-  exit!
-end
-
-# Delete supplied host, port and listen-host CLI arguments
-["--host", "--port", "--listen-host"].each do |arg|
-  ARGV.delete(args(arg))
-  ARGV.delete(arg)
-end
-
-env = { "NODE_PATH" => NODE_MODULES_PATH.shellescape }
-
-cmd = [
-  "#{NODE_MODULES_PATH}/.bin/webpack-dev-server", "--progress", "--color",
-  "--config", WEBPACK_CONFIG,
-  "--host", LISTEN_HOST_ADDR,
-  "--public", "#{HOSTNAME}:#{PORT}",
-  "--port", PORT.to_s
-] + ARGV
-
-Dir.chdir(APP_PATH) do
-  exec env, *cmd
-end
+# frozen_string_literal: true
+#
+# This file was generated by Bundler.
+#
+# The application 'webpack-dev-server' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+require "pathname"
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
+  Pathname.new(__FILE__).realpath)
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("webpacker", "webpack-dev-server")
diff --git a/boxfile.yml b/boxfile.yml
index 59a66d87b..6b904e07d 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -42,6 +42,7 @@ run.config:
 
   fs_watch: true
 
+
 deploy.config:
   extra_steps:
     - NODE_ENV=production bundle exec rake assets:precompile
@@ -60,6 +61,7 @@ deploy.config:
     web.web:
       - bundle exec rake db:migrate:setup
 
+
 web.web:
   start:
     nginx: nginx -c /app/nanobox/nginx-web.conf
@@ -78,6 +80,7 @@ web.web:
     data.storage:
       - public/system
 
+
 web.stream:
   start:
     nginx: nginx -c /app/nanobox/nginx-stream.conf
@@ -91,8 +94,13 @@ web.stream:
   writable_dirs:
     - tmp
 
+
 worker.sidekiq:
-  start: bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push -L /app/log/sidekiq.log
+  start:
+    default: bundle exec sidekiq -c 5 -q default -L /app/log/sidekiq.log
+    mailers: bundle exec sidekiq -c 5 -q mailers -L /app/log/sidekiq.log
+    pull: bundle exec sidekiq -c 5 -q pull -L /app/log/sidekiq.log
+    push: bundle exec sidekiq -c 5 -q push -L /app/log/sidekiq.log
 
   writable_dirs:
     - tmp
@@ -105,50 +113,78 @@ worker.sidekiq:
     data.storage:
       - public/system
 
-  cron:
-    - id: generate_static_gifs
-      schedule: '*/15 * * * *'
-      command: 'bundle exec rake mastodon:maintenance:add_static_avatars'
 
-    - id: update_counter_caches
-      schedule: '50 * * * *'
-      command: 'bundle exec rake mastodon:maintenance:update_counter_caches'
+worker.cron_only:
+  start: sleep 365d
+
+  writable_dirs:
+    - tmp
+
+  log_watch:
+    rake: 'log/production.log'
 
-    # runs feeds:clear, media:clear, users:clear, and push:refresh
-    - id: do_daily_tasks
-      schedule: '00 00 * * *'
-      command: 'bundle exec rake mastodon:daily'
+  network_dirs:
+    data.storage:
+      - public/system
 
-    - id: clear_silenced_media
-      schedule: '10 00 * * *'
-      command: 'bundle exec rake mastodon:media:remove_silenced'
+  cron:
+    # 20:00 (8 pm), server time: send out the daily digest emails to everyone
+    # who opted to receive one
+    - id: send_digest_emails
+      schedule: '00 20 * * *'
+      command: 'bundle exec rake mastodon:emails:digest'
 
+    # 00:10 (ten past midnight), server time: remove local copies of remote
+    # users' media once they are older than a certain age (use NUM_DAYS evar to
+    # change this from the default of 7 days)
     - id: clear_remote_media
-      schedule: '20 00 * * *'
+      schedule: '10 00 * * *'
       command: 'bundle exec rake mastodon:media:remove_remote'
 
+    # 00:20 (twenty past midnight), server time: remove subscriptions to remote
+    # users that nobody follows locally (anymore)
     - id: clear_unfollowed_subs
-      schedule: '30 00 * * *'
+      schedule: '20 00 * * *'
       command: 'bundle exec rake mastodon:push:clear'
 
-    - id: send_digest_emails
-      schedule: '00 20 * * *'
-      command: 'bundle exec rake mastodon:emails:digest'
-
+    # 00:30 (half past midnight), server time: update local copies of remote
+    # users' avatars to match whatever they currently have set on their profile
+    - id: update_remote_avatars
+      schedule: '30 00 * * *'
+      command: 'bundle exec rake mastodon:media:redownload_avatars'
+
+    ############################################################################
+    # This task is one you might want to enable, or might not. It keeps disk
+    # usage low, but makes "shadow bans" (scenarios where the user is silenced,
+    # but not intended to be made aware that the silencing has occurred) much
+    # more difficult to put in place, as users would then notice their media is
+    # vanishing on a regular basis. Enable it if you aren't worried about users
+    # knowing they've been silenced (on the instance level), and want to save
+    # disk space. Leave it disabled otherwise.
+    ############################################################################
+    # # 00:00 (midnight), server time: remove media posted by silenced users
+    # - id: clear_silenced_media
+    #   schedule: '00 00 * * *'
+    #   command: 'bundle exec rake mastodon:media:remove_silenced'
+
+    ############################################################################
     # The following two tasks can be uncommented to automatically open and close
     # registrations on a schedule. The format of 'schedule' is a standard cron
     # time expression: minute hour day month day-of-week; search for "cron
     # time expressions" for more info on how to set these up. The examples here
     # open registration only from 8 am to 4 pm, server time.
-    #
+    ############################################################################
+    # # 08:00 (8 am), server time: open registrations so new users can join
     # - id: open_registrations
     #   schedule: '00 08 * * *'
     #   command: 'bundle exec rake mastodon:settings:open_registrations'
     #
+    # # 16:00 (4 pm), server time: close registrations so new users *can't* join
     # - id: close_registrations
     #   schedule: '00 16 * * *'
     #   command: 'bundle exec rake mastodon:settings:close_registrations'
 
+
 data.db:
   image: nanobox/postgresql:9.5
 
@@ -170,6 +206,7 @@ data.db:
           curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
         done
 
+
 data.redis:
   image: nanobox/redis:3.0
 
@@ -189,6 +226,7 @@ data.redis:
           curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
         done
 
+
 data.storage:
   image: nanobox/unfs:0.9
 
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index f198eebac..f7cf89dff 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -10,7 +10,7 @@
       "line": 122,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -29,7 +29,7 @@
       "line": 128,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -48,7 +48,7 @@
       "line": 35,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -60,25 +60,6 @@
     {
       "warning_type": "Dynamic Render Path",
       "warning_code": 15,
-      "fingerprint": "3b0a20b08aef13cf8cf865384fae0cfd3324d8200a83262bf4abbc8091b5fec5",
-      "check_name": "Render",
-      "message": "Render path contains parameter value",
-      "file": "app/views/admin/custom_emojis/index.html.haml",
-      "line": 31,
-      "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
-      "code": "render(action => filtered_custom_emojis.page(params[:page]), {})",
-      "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}],
-      "location": {
-        "type": "template",
-        "template": "admin/custom_emojis/index"
-      },
-      "user_input": "params[:page]",
-      "confidence": "Weak",
-      "note": ""
-    },
-    {
-      "warning_type": "Dynamic Render Path",
-      "warning_code": 15,
       "fingerprint": "44d3f14e05d8fbb5b23e13ac02f15aa38b2a2f0f03b9ba76bab7f98e155a4a4e",
       "check_name": "Render",
       "message": "Render path contains parameter value",
@@ -105,7 +86,7 @@
       "line": 131,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -124,7 +105,7 @@
       "line": 106,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -134,13 +115,32 @@
       "note": ""
     },
     {
+      "warning_type": "Dynamic Render Path",
+      "warning_code": 15,
+      "fingerprint": "8d843713d99e8403f7992f3e72251b633817cf9076ffcbbad5613859d2bbc127",
+      "check_name": "Render",
+      "message": "Render path contains parameter value",
+      "file": "app/views/admin/custom_emojis/index.html.haml",
+      "line": 31,
+      "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+      "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})",
+      "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}],
+      "location": {
+        "type": "template",
+        "template": "admin/custom_emojis/index"
+      },
+      "user_input": "params[:page]",
+      "confidence": "Weak",
+      "note": ""
+    },
+    {
       "warning_type": "SQL Injection",
       "warning_code": 0,
       "fingerprint": "9ccb9ba6a6947400e187d515e0bf719d22993d37cfc123c824d7fafa6caa9ac3",
       "check_name": "SQL",
       "message": "Possible SQL injection",
       "file": "lib/mastodon/snowflake.rb",
-      "line": 86,
+      "line": 87,
       "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/",
       "code": "connection.execute(\"        CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n        RETURNS bigint AS\\n        $$\\n          DECLARE\\n            time_part bigint;\\n            sequence_base bigint;\\n            tail bigint;\\n          BEGIN\\n            time_part := (\\n              -- Get the time in milliseconds\\n              ((date_part('epoch', now()) * 1000))::bigint\\n              -- And shift it over two bytes\\n              << 16);\\n\\n            sequence_base := (\\n              'x' ||\\n              -- Take the first two bytes (four hex characters)\\n              substr(\\n                -- Of the MD5 hash of the data we documented\\n                md5(table_name ||\\n                  '#{SecureRandom.hex(16)}' ||\\n                  time_part::text\\n                ),\\n                1, 4\\n              )\\n            -- And turn it into a bigint\\n            )::bit(16)::bigint;\\n\\n            -- Finally, add our sequence number to our base, and chop\\n            -- it to the last two bytes\\n            tail := (\\n              (sequence_base + nextval(table_name || '_id_seq'))\\n              & 65535);\\n\\n            -- Return the time part and the sequence part. OR appears\\n            -- faster here than addition, but they're equivalent:\\n            -- time_part has no trailing two bytes, and tail is only\\n            -- the last two bytes.\\n            RETURN time_part | tail;\\n          END\\n        $$ LANGUAGE plpgsql VOLATILE;\\n\")",
       "render_path": null,
@@ -182,7 +182,7 @@
       "line": 95,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -240,7 +240,7 @@
       "line": 125,
       "link": "http://brakemanscanner.org/docs/warning_types/link_to_href",
       "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)",
-      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}],
+      "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}],
       "location": {
         "type": "template",
         "template": "admin/accounts/show"
@@ -269,6 +269,6 @@
       "note": ""
     }
   ],
-  "updated": "2017-10-07 19:24:02 +0200",
+  "updated": "2017-10-20 00:00:54 +0900",
   "brakeman_version": "4.0.1"
 }
diff --git a/config/deploy.rb b/config/deploy.rb
index 33b88b109..3fd149f21 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-lock '3.8.2'
+lock '3.10.0'
 
 set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
 set :branch, ENV.fetch('BRANCH', 'master')
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index b35e5c09a..08a96f727 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -46,6 +46,7 @@ ignore_missing:
   - 'terms.body_html'
   - 'application_mailer.salutation'
   - 'errors.500'
+
 ignore_unused:
   - 'activemodel.errors.*'
   - 'activerecord.attributes.*'
@@ -58,3 +59,4 @@ ignore_unused:
   - 'errors.messages.*'
   - 'activerecord.errors.models.doorkeeper/*'
   - 'errors.429'
+  - 'admin.accounts.roles.*'
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index 2c82a91db..14bd034e6 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -7,60 +7,76 @@ Paperclip.interpolates :filename do |attachment, style|
   [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
 end
 
-Paperclip::Attachment.default_options[:use_timestamp]  = false
+Paperclip::Attachment.default_options.merge!(
+  use_timestamp: false,
+  path: ':class/:attachment/:id_partition/:style/:filename',
+  storage: :fog
+)
 
 if ENV['S3_ENABLED'] == 'true'
-  Aws.eager_autoload!(services: %w(S3))
+  require 'fog/aws'
 
-  Paperclip::Attachment.default_options[:storage]        = :s3
-  Paperclip::Attachment.default_options[:s3_protocol]    = ENV.fetch('S3_PROTOCOL') { 'https' }
-  Paperclip::Attachment.default_options[:url]            = ':s3_domain_url'
-  Paperclip::Attachment.default_options[:s3_host_name]   = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV.fetch('S3_REGION')}.amazonaws.com" }
-  Paperclip::Attachment.default_options[:path]           = '/:class/:attachment/:id_partition/:style/:filename'
-  Paperclip::Attachment.default_options[:s3_headers]     = { 'Cache-Control' => 'max-age=315576000' }
-  Paperclip::Attachment.default_options[:s3_permissions] = ENV.fetch('S3_PERMISSION') { 'public-read' }
-  Paperclip::Attachment.default_options[:s3_region]      = ENV.fetch('S3_REGION') { 'us-east-1' }
+  s3_protocol           = ENV.fetch('S3_PROTOCOL') { 'https' }
+  s3_hostname           = ENV.fetch('S3_HOSTNAME') { "s3-#{ENV['S3_REGION']}.amazonaws.com" }
+  aws_signature_version = ENV['S3_SIGNATURE_VERSION'] == 's3' ? 2 : ENV['S3_SIGNATURE_VERSION'].to_i
+  aws_signature_version = 4 if aws_signature_version.zero?
 
-  Paperclip::Attachment.default_options[:s3_credentials] = {
-    bucket: ENV.fetch('S3_BUCKET'),
-    access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
-    secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
-  }
-
-  unless ENV['S3_ENDPOINT'].blank?
-    Paperclip::Attachment.default_options[:s3_options] = {
-      endpoint: ENV['S3_ENDPOINT'],
-      signature_version: ENV['S3_SIGNATURE_VERSION'] || 'v4',
-      force_path_style: true,
+  Paperclip::Attachment.default_options.merge!(
+    fog_credentials: {
+      provider: 'AWS',
+      aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
+      aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
+      aws_signature_version: aws_signature_version,
+      region: ENV.fetch('S3_REGION') { 'us-east-1' },
+      scheme: s3_protocol,
+      host: s3_hostname
+    },
+    fog_directory: ENV['S3_BUCKET'],
+    fog_options: {
+      acl: ENV.fetch('S3_PERMISSION') { 'public-read' },
+      cache_control: 'max-age=315576000',
     }
+  )
 
-    Paperclip::Attachment.default_options[:url] = ':s3_path_url'
+  if ENV.has_key?('S3_ENDPOINT')
+    Paperclip::Attachment.default_options[:fog_credentials].merge!(
+      endpoint: ENV['S3_ENDPOINT'],
+      path_style: true
+    )
+    Paperclip::Attachment.default_options[:fog_host] = "#{s3_protocol}://#{s3_hostname}/#{ENV['S3_BUCKET']}"
   end
 
-  unless ENV['S3_CLOUDFRONT_HOST'].blank?
-    Paperclip::Attachment.default_options[:url]           = ':s3_alias_url'
-    Paperclip::Attachment.default_options[:s3_host_alias] = ENV['S3_CLOUDFRONT_HOST']
+  if ENV.has_key?('S3_CLOUDFRONT_HOST')
+    Paperclip::Attachment.default_options[:fog_host] = "#{s3_protocol}://#{ENV['S3_CLOUDFRONT_HOST']}"
   end
 elsif ENV['SWIFT_ENABLED'] == 'true'
+  require 'fog/openstack'
+
   Paperclip::Attachment.default_options.merge!(
-    path: ':class/:attachment/:id_partition/:style/:filename',
-    storage: :fog,
     fog_credentials: {
       provider: 'OpenStack',
-      openstack_username: ENV.fetch('SWIFT_USERNAME'),
-      openstack_project_name: ENV.fetch('SWIFT_TENANT'),
-      openstack_tenant: ENV.fetch('SWIFT_TENANT'), # Some OpenStack-v2 ignores project_name but needs tenant
-      openstack_api_key: ENV.fetch('SWIFT_PASSWORD'),
-      openstack_auth_url: ENV.fetch('SWIFT_AUTH_URL'),
-      openstack_domain_name: ENV['SWIFT_DOMAIN_NAME'] || 'default',
+      openstack_username: ENV['SWIFT_USERNAME'],
+      openstack_project_name: ENV['SWIFT_TENANT'],
+      openstack_tenant: ENV['SWIFT_TENANT'], # Some OpenStack-v2 ignores project_name but needs tenant
+      openstack_api_key: ENV['SWIFT_PASSWORD'],
+      openstack_auth_url: ENV['SWIFT_AUTH_URL'],
+      openstack_domain_name: ENV.fetch('SWIFT_DOMAIN_NAME') { 'default' },
       openstack_region: ENV['SWIFT_REGION'],
-      openstack_cache_ttl: ENV['SWIFT_CACHE_TTL'] || 60,
+      openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
     },
-    fog_directory: ENV.fetch('SWIFT_CONTAINER'),
+    fog_directory: ENV['SWIFT_CONTAINER'],
     fog_host: ENV['SWIFT_OBJECT_URL'],
     fog_public: true
   )
 else
-  Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
-  Paperclip::Attachment.default_options[:url]  = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
+  require 'fog/local'
+
+  Paperclip::Attachment.default_options.merge!(
+    fog_credentials: {
+      provider: 'Local',
+      local_root: ENV.fetch('PAPERCLIP_ROOT_PATH') { Rails.root.join('public', 'system') },
+    },
+    fog_directory: '',
+    fog_host: ENV.fetch('PAPERCLIP_ROOT_URL') { '/system' }
+  )
 end
diff --git a/config/initializers/statsd.rb b/config/initializers/statsd.rb
index 17a176174..45702ac94 100644
--- a/config/initializers/statsd.rb
+++ b/config/initializers/statsd.rb
@@ -4,7 +4,7 @@ if ENV['STATSD_ADDR'].present?
   host, port = ENV['STATSD_ADDR'].split(':')
 
   statsd = ::Statsd.new(host, port)
-  statsd.namespace = ['Mastodon', Rails.env].join('.')
+  statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
 
   ::NSA.inform_statsd(statsd) do |informant|
     informant.collect(:action_controller, :web)
diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml
new file mode 100644
index 000000000..8628d6677
--- /dev/null
+++ b/config/locales/activerecord.zh-CN.yml
@@ -0,0 +1,13 @@
+---
+zh-CN:
+  activerecord:
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: 只能使用字母、数字和下划线
+        status:
+          attributes:
+            reblog:
+              taken: 已经被转嘟过
diff --git a/config/locales/devise.oc.yml b/config/locales/devise.oc.yml
index 5cccb48ff..266f87373 100644
--- a/config/locales/devise.oc.yml
+++ b/config/locales/devise.oc.yml
@@ -9,7 +9,7 @@ oc:
       already_authenticated: Sètz ja connectat.
       inactive: Vòstre compte es pas encara activat.
       invalid: Corrièl o senhal invalid.
-      last_attempt: Vos demòra un ensag abans que vòstre compte siasgue blocat.
+      last_attempt: Vos demòra un ensag abans que vòstre compte siasque blocat.
       locked: Vòstre compte es blocat.
       not_found_in_database: Corrièl o senhal invalid.
       timeout: Vòstra session s’a acabat. Mercés de vos tornar connectar per contunhar.
diff --git a/config/locales/devise.zh-CN.yml b/config/locales/devise.zh-CN.yml
index 8cc814224..5560e12b3 100644
--- a/config/locales/devise.zh-CN.yml
+++ b/config/locales/devise.zh-CN.yml
@@ -2,24 +2,24 @@
 zh-CN:
   devise:
     confirmations:
-      confirmed: 成功验证您的邮箱地址。
-      send_instructions: 您的电子邮箱将在几分钟后收到一封邮箱确认邮件。
-      send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封确认帐号的邮件。
+      confirmed: 成功验证你的邮箱地址。
+      send_instructions: 你的电子邮箱将在几分钟后收到一封确认邮件。如果没有,请检查你的垃圾邮箱。
+      send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封确认注册的邮件。如果没有,请检查你的垃圾邮箱。
     failure:
-      already_authenticated: 您已经登录。
-      inactive: 您还没有激活帐户。
-      invalid: " %{authentication_keys} 或密码错误。"
-      last_attempt: 您还有最后一次尝试机会,再次失败您的帐号将被锁定。
-      locked: 您的帐号已被锁定。
+      already_authenticated: 你已经登录。
+      inactive: 你还没有激活帐户。
+      invalid: "%{authentication_keys}或密码错误。"
+      last_attempt: 你还有最后一次尝试机会,再次失败你的帐户将被锁定。
+      locked: 你的帐户已被锁定。
       not_found_in_database: "%{authentication_keys}或密码错误。"
-      timeout: 您已登录超时,请重新登录。
+      timeout: 你已登录超时,请重新登录。
       unauthenticated: 继续操作前请注册或者登录。
-      unconfirmed: 继续操作前请先确认您的帐号。
+      unconfirmed: 继续操作前请先确认你的帐户。
     mailer:
       confirmation_instructions:
         subject: Mastodon 帐户确认信息
       email_changed:
-        subject: Mastodon 电邮已被修改
+        subject: Mastodon 电子邮件地址已被修改
       password_change:
         subject: Mastodon 密码已被重置
       reset_password_instructions:
@@ -30,33 +30,33 @@ zh-CN:
       failure: 由于%{reason},无法从%{kind}获得授权。
       success: 成功地从%{kind}获得授权。
     passwords:
-      no_token: 无重置邮件不可访问密码重置页面。如果您是从重置邮件来到了这个页面,请确保您输入的URL完整的。
-      send_instructions: 几分钟后,您将收到重置密码的电子邮件。
-      send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封找回密码的邮件。
-      updated: 您的密码已修改成功,您现在已登录。
-      updated_not_active: 您的密码已修改成功。
+      no_token: 你必须通过密码重置邮件才能访问这个页面。如果你确实如此,请确保你输入的 URL 是完整的。
+      send_instructions: 几分钟后,你将收到重置密码的电子邮件。如果没有,请检查你的垃圾邮箱。
+      send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封找回密码的邮件。如果没有,请检查你的垃圾邮箱。
+      updated: 你的密码已修改成功,你现在已登录。
+      updated_not_active: 你的密码已修改成功。
     registrations:
-      destroyed: 再见!您的帐户已成功注销。我们希望很快可以再见到您。
-      signed_up: 欢迎!您已注册成功。
-      signed_up_but_inactive: 您已注册,但尚未激活帐号。
-      signed_up_but_locked: 您已注册,但帐号被锁定了。
-      signed_up_but_unconfirmed: 一封带有确认链接的邮件已经发送至您的邮箱,请检查邮箱(包括垃圾邮箱),并点击该链接激活您的帐号。
-      update_needs_confirmation: 信息更新成功,但我们需要验证您的新电子邮件地址,请检查邮箱(包括垃圾邮箱),并点击该链接激活您的帐号。
-      updated: 帐号资料更新成功。
+      destroyed: 再见!你的帐户已成功注销。我们希望很快可以再见到你。
+      signed_up: 欢迎!你已注册成功。
+      signed_up_but_inactive: 你已注册,但尚未激活帐户。
+      signed_up_but_locked: 你已注册,但帐户被锁定了。
+      signed_up_but_unconfirmed: 一封带有确认链接的邮件已经发送至你的邮箱,请点击邮件中的链接以激活你的帐户。如果没有,请检查你的垃圾邮箱。
+      update_needs_confirmation: 信息更新成功,但我们需要验证你的新电子邮件地址,请点击邮件中的链接以确认。如果没有,请检查你的垃圾邮箱。
+      updated: 帐户资料更新成功。
     sessions:
-      already_signed_out: 已经退出成功。
+      already_signed_out: 已成功登出。
       signed_in: 登录成功。
-      signed_out: 退出成功。
+      signed_out: 登出成功。
     unlocks:
-      send_instructions: 几分钟后,您将收到一封解锁帐号的邮件。
-      send_paranoid_instructions: 如果您的邮箱存在于我们的数据库中,您将收到一封解锁帐号的邮件。
-      unlocked: 您的帐号已成功解锁,您现在已登录。
+      send_instructions: 几分钟后,你将收到一封解锁帐户的邮件。如果没有,请检查你的垃圾邮箱。
+      send_paranoid_instructions: 如果你的邮箱存在于我们的数据库中,你将收到一封解锁帐户的邮件。如果没有,请检查你的垃圾邮箱。
+      unlocked: 你的帐户已成功解锁。登录以继续。
   errors:
     messages:
-      already_confirmed: 已经确认,请重新登录。
-      confirmation_period_expired: 注册帐号后须在%{period}以内确认。请重新注册。
-      expired: 邮件确认已过期,请重新注册。
-      not_found: 找不到。
-      not_locked: 未锁定。
+      already_confirmed: 已经确认成功,请尝试登录
+      confirmation_period_expired: 必须在 %{period}以内确认。请重新发起请求
+      expired: 已过期。请重新发起请求
+      not_found: 找不到
+      not_locked: 未被锁定
       not_saved:
-        other: 发生%{count}个错误,导致%{resource}保存失败:
+        other: 发生 %{count} 个错误,导致%{resource}保存失败:
diff --git a/config/locales/doorkeeper.pl.yml b/config/locales/doorkeeper.pl.yml
index fa4324e4d..33f133c06 100644
--- a/config/locales/doorkeeper.pl.yml
+++ b/config/locales/doorkeeper.pl.yml
@@ -59,7 +59,7 @@ pl:
       error:
         title: Wystapił błąd
       new:
-        able_to: Będzie w stanie
+        able_to: Uzyska
         prompt: Aplikacja %{client_name} prosi o dostęp do Twojego konta
         title: Wymagana jest autoryzacja
       show:
@@ -114,6 +114,6 @@ pl:
       application:
         title: Uwierzytelnienie OAuth jest wymagane
     scopes:
-      follow: śledzenie, blokowanie, usuwanie blokady, anulowanie śledzenia kont
+      follow: możliwość śledzenia, blokowania, usuwania blokad, anulowania śledzenia kont
       read: dostęp do odczytu danych konta
-      write: publikowanie wpisów w Twoim imieniu
+      write: możliwość publikowania wpisów w Twoim imieniu
diff --git a/config/locales/doorkeeper.zh-CN.yml b/config/locales/doorkeeper.zh-CN.yml
index 12b38b81f..0eb5a0ab6 100644
--- a/config/locales/doorkeeper.zh-CN.yml
+++ b/config/locales/doorkeeper.zh-CN.yml
@@ -3,15 +3,16 @@ zh-CN:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: 名称
-        redirect_uri: 登录回调地址
+        name: 应用名称
+        redirect_uri: 重定向 URI
         scopes: 权限范围
+        website: 应用网站
     errors:
       models:
         doorkeeper/application:
           attributes:
             redirect_uri:
-              fragment_present: 不能包含片段(#)
+              fragment_present: 不能包含网址片段(#)
               invalid_uri: 必须是有效的 URL 格式
               relative_uri: 必须是绝对的 URL 地址
               secured_uri: 必须是 HTTPS/SSL 的 URL 地址
@@ -30,60 +31,65 @@ zh-CN:
       form:
         error: 抱歉! 提交信息的时候遇到了下面的错误
       help:
-        native_redirect_uri: 使用 %{native_redirect_uri} 作为本地测试
+        native_redirect_uri: 本地测试请使用 %{native_redirect_uri}
         redirect_uri: 每行只能有一个 URL
-        scopes: 用空格隔开权限范围,留空则使用默认设置
+        scopes: 用空格分割权限范围,留空则使用默认设置
       index:
-        callback_url: 登录回调地址
+        application: 应用
+        callback_url: 回调 URL
+        delete: 删除
         name: 名称
         new: 创建新应用
+        scopes: 权限范围
+        show: 显示
         title: 你的应用
       new:
         title: 创建新应用
       show:
         actions: 操作
         application_id: 应用 ID
-        callback_urls: 登录回调地址
+        callback_urls: 回调 URL
         scopes: 权限范围
-        secret: 私钥
+        secret: 应用密钥
         title: 应用:%{name}
     authorizations:
       buttons:
-        authorize: 授权
-        deny: 拒绝
+        authorize: 同意授权
+        deny: 拒绝授权
       error:
-        title: 存在错误
+        title: 发生错误
       new:
-        able_to: 此应用将会
-        prompt: 授权 %{client_name} 使用你的帐号?
-        title: 需要你授权
+        able_to: 此应用将能够
+        prompt: 授权 %{client_name} 使用你的帐户?
+        title: 需要授权
       show:
-        title: Copy this authorization code and paste it to the application.
+        title: 接下来请复制此处的授权代码并粘贴到应用中。
     authorized_applications:
       buttons:
-        revoke: 注销
+        revoke: 撤销授权
       confirmations:
-        revoke: 确定要注销此应用的认证信息吗?
+        revoke: 确定要撤销对此应用的授权吗?
       index:
         application: 应用
         created_at: 授权时间
         date_format: "%Y-%m-%d %H:%M:%S"
-        title: 你授权的应用列表
+        scopes: 权限范围
+        title: 已授权的应用列表
     errors:
       messages:
-        access_denied: 用户或服务器拒绝了请求
-        credential_flow_not_configured: Resource Owner Password Credentials flow failed,原因是 Doorkeeper.configure.resource_owner_from_credentials 尚未设置。
-        invalid_client: 由于未知、不支持或没有客户端,认证失败
-        invalid_grant: 授权方式无效,或者登录回调地址无效、过期或已被撤销
+        access_denied: 资源所有者或服务器拒绝了请求
+        credential_flow_not_configured: 由于 Doorkeeper.configure.resource_owner_from_credentials 尚未配置,应用验证授权流程失败。
+        invalid_client: 由于应用信息未知、未提交认证信息或使用了不支持的认证方式,认证失败
+        invalid_grant: 授权方式无效、过期或已被撤销、与授权请求中的回调地址不一致,或使用了其他应用的回调地址
         invalid_redirect_uri: 无效的登录回调地址
-        invalid_request: 这个请求缺少必要的参数,或者参数值、格式不正确
-        invalid_resource_owner: 资源所有者认证无效或没有所有者
-        invalid_scope: 请求范围无效、未知或格式不正确
+        invalid_request: 请求缺少必要的参数,或者参数值、格式不正确
+        invalid_resource_owner: 资源所有者认证无效,或找不到所有者
+        invalid_scope: 请求的权限范围无效、未知或格式不正确
         invalid_token:
           expired: 访问令牌已过期
           revoked: 访问令牌已被吊销
           unknown: 访问令牌无效
-        resource_owner_authenticator_not_configured: Resource Owner find failed,原因是 Doorkeeper.configure.resource_owner_authenticator 尚未设置。
+        resource_owner_authenticator_not_configured: 由于 Doorkeeper.configure.resource_owner_authenticator 尚未配置,查找资源所有者失败。
         server_error: 服务器异常,无法处理请求
         temporarily_unavailable: 服务器维护中或负载过高,暂时无法处理请求
         unauthorized_client: 未授权的应用,请求无法执行
@@ -99,16 +105,15 @@ zh-CN:
           notice: 应用修改成功
       authorized_applications:
         destroy:
-          notice: 已成功注销了应用的认证信息
+          notice: 已成功撤销对此应用的授权
     layouts:
       admin:
         nav:
           applications: 应用
-          home: 首页
           oauth2_provider: OAuth2 提供商
       application:
-        title: OAuth 认证
+        title: 需要 OAuth 认证
     scopes:
-      follow: 关注(或取消关注),屏蔽(或取消屏蔽)用户
-      read: 读取你的账户数据
+      follow: 关注(或取消关注)、屏蔽(或取消屏蔽)用户
+      read: 读取你的帐户数据
       write: 为你发表嘟文
diff --git a/config/locales/en.yml b/config/locales/en.yml
index d5c46470c..cebf704ce 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -43,11 +43,12 @@ en:
     people_followed_by: People whom %{name} follows
     people_who_follow: People who follow %{name}
     posts: Toots
-    posts_with_replies: Toots with replies
+    posts_with_replies: Toots and replies
     remote_follow: Remote follow
     reserved_username: The username is reserved
     roles:
       admin: Admin
+      moderator: Mod
     unfollow: Unfollow
   admin:
     account_moderation_notes:
@@ -59,13 +60,19 @@ en:
       destroyed_msg: Moderation note successfully destroyed!
     accounts:
       are_you_sure: Are you sure?
+      by_domain: Domain
       confirm: Confirm
       confirmed: Confirmed
+      demote: Demote
+      disable: Disable
       disable_two_factor_authentication: Disable 2FA
+      disabled: Disabled
       display_name: Display name
       domain: Domain
       edit: Edit
       email: E-mail
+      enable: Enable
+      enabled: Enabled
       feed_url: Feed URL
       followers: Followers
       followers_url: Followers URL
@@ -77,7 +84,9 @@ en:
         local: Local
         remote: Remote
         title: Location
+      login_status: Login status
       media_attachments: Media attachments
+      memorialize: Turn into memoriam
       moderation:
         all: All
         silenced: Silenced
@@ -94,6 +103,7 @@ en:
       outbox_url: Outbox URL
       perform_full_suspension: Perform full suspension
       profile_url: Profile URL
+      promote: Promote
       protocol: Protocol
       public: Public
       push_subscription_expires: PuSH subscription expires
@@ -101,6 +111,11 @@ en:
       reset: Reset
       reset_password: Reset password
       resubscribe: Resubscribe
+      role: Permissions
+      roles:
+        admin: Administrator
+        moderator: Moderator
+        user: User
       salmon_url: Salmon URL
       search: Search
       shared_inbox_url: Shared Inbox URL
@@ -133,6 +148,7 @@ en:
       listed: Listed
       new:
         title: Add new custom emoji
+      overwrite: Overwrite
       shortcode: Shortcode
       shortcode_hint: At least 2 characters, only alphanumeric characters and underscores
       title: Custom emojis
@@ -377,6 +393,7 @@ en:
       following: Following list
       muting: Muting list
     upload: Upload
+  in_memoriam_html: In Memoriam.
   keyword_mutes:
     add_keyword: Add keyword
     edit: Edit
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 0d9c227f3..55588d111 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -437,7 +437,7 @@ fr:
       action_favourite: Ajouter aux favoris
       title: "%{name} vous a mentionné·e"
     reblog:
-      title: "%{name} a partagé⋅e votre statut"
+      title: "%{name} a partagé votre statut"
   remote_follow:
     acct: Entrez votre pseudo@instance depuis lequel vous voulez suivre ce⋅tte utilisateur⋅rice
     missing_resource: L’URL de redirection n’a pas pu être trouvée
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 391556040..82b642b5b 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -59,13 +59,19 @@ ja:
       destroyed_msg: モデレーションメモを削除しました
     accounts:
       are_you_sure: 本当に実行しますか?
+      by_domain: ドメイン
       confirm: 確認
       confirmed: 確認済み
+      demote: 降格
+      disable: 無効化
       disable_two_factor_authentication: 二段階認証を無効にする
+      disabled: 無効
       display_name: 表示名
       domain: ドメイン
       edit: 編集
       email: E-mail
+      enable: 有効化
+      enabled: 有効
       feed_url: フィードURL
       followers: フォロワー数
       followers_url: Followers URL
@@ -77,7 +83,9 @@ ja:
         local: ローカル
         remote: リモート
         title: ロケーション
+      login_status: ログイン
       media_attachments: 添付されたメディア
+      memorialize: 追悼アカウント化
       moderation:
         all: すべて
         silenced: サイレンス中
@@ -94,6 +102,7 @@ ja:
       outbox_url: Outbox URL
       perform_full_suspension: 完全に活動停止させる
       profile_url: プロフィールURL
+      promote: 昇格
       protocol: プロトコル
       public: パブリック
       push_subscription_expires: PuSH購読期限
@@ -101,6 +110,11 @@ ja:
       reset: リセット
       reset_password: パスワード再設定
       resubscribe: 再講読
+      role: 役割
+      roles:
+        admin: 管理者
+        moderator: モデレーター
+        user: ユーザー
       salmon_url: Salmon URL
       search: 検索
       shared_inbox_url: Shared Inbox URL
@@ -130,11 +144,16 @@ ja:
       enable: 有効化
       enabled_msg: 絵文字を有効化しました
       image_hint: 50KBまでのPNG画像を利用できます。
+      listed: 収載
       new:
         title: 新規カスタム絵文字の追加
+      overwrite: 上書き
       shortcode: ショートコード
       shortcode_hint: 2文字以上の半角英数字とアンダーバーのみ利用できます。
       title: カスタム絵文字
+      unlisted: 未収載
+      update_failed_msg: 絵文字を更新できませんでした
+      updated_msg: 絵文字の更新に成功しました
       upload: アップロード
     domain_blocks:
       add_new: 新規追加
@@ -373,6 +392,7 @@ ja:
       following: フォロー中のアカウントリスト
       muting: ミュートしたアカウントリスト
     upload: アップロード
+  in_memoriam_html: 故人を偲んで
   landing_strip_html: "<strong>%{name}</strong> さんはインスタンス %{link_to_root_path} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。"
   landing_strip_signup_html: もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。
   media_attachments:
@@ -390,8 +410,8 @@ ja:
         one: "新しい1件の通知 \U0001F418"
         other: "新しい%{count}件の通知 \U0001F418"
     favourite:
-      body: 'あなたのトゥートが %{name} さんにお気に入り登録されました:'
-      subject: "%{name} さんがあなたのトゥートをお気に入りに登録しました"
+      body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:"
+      subject: "%{name} さんにお気に入りに登録されました"
     follow:
       body: "%{name} さんにフォローされています"
       subject: "%{name} さんにフォローされています"
@@ -402,8 +422,8 @@ ja:
       body: "%{name} さんから返信がありました:"
       subject: "%{name} さんに返信されました"
     reblog:
-      body: 'あなたのトゥートが %{name} さんにブーストされました:'
-      subject: あなたのトゥートが %{name} さんにブーストされました
+      body: "%{name} さんにブーストされた、あなたのトゥートがあります:"
+      subject: "%{name} さんにブーストされました"
   number:
     human:
       decimal_units:
diff --git a/config/locales/oc.yml b/config/locales/oc.yml
index 0d2a8c2f6..914cc7e9d 100644
--- a/config/locales/oc.yml
+++ b/config/locales/oc.yml
@@ -59,13 +59,18 @@ oc:
       destroyed_msg: Nòta de moderacion ben suprimida !
     accounts:
       are_you_sure: Sètz segur ?
+      by_domain: Domeni
       confirm: Confirmar
       confirmed: Confirmat
+      disable: Desactivar
       disable_two_factor_authentication: Desactivar 2FA
+      disabled: Desactivat
       display_name: Escais-nom
       domain: Domeni
       edit: Modificar
       email: Corrièl
+      enable: Activar
+      enabled: Activat
       feed_url: Flux URL
       followers: Seguidors
       followers_url: URL dels seguidors
@@ -77,7 +82,9 @@ oc:
         local: Locals
         remote: Alonhats
         title: Emplaçament
+      login_status: Estat formulari de connexion
       media_attachments: Mèdias ajustats
+      memorialize: Passar en memorial
       moderation:
         all: Tot
         silenced: Rescondut
@@ -130,11 +137,16 @@ oc:
       enable: Activar
       enabled_msg: Aqueste emoji es ben activat
       image_hint: PNG cap a 50Ko
+      listed: Listat
       new:
         title: Ajustar un nòu emoji personal
+      overwrite: Remplaçar
       shortcode: Acorchi
       shortcode_hint: Almens 2 caractèrs, solament alfanumerics e jonhent bas
       title: Emojis personals
+      unlisted: Pas listat
+      update_failed_msg: Mesa a jorn de l’emoji fracasada
+      updated_msg: Emoji ben mes a jorn !
       upload: Enviar
     domain_blocks:
       add_new: N’ajustar un nòu
@@ -451,6 +463,7 @@ oc:
       following: Lista de mond que seguètz
       muting: Lista de mond que volètz pas legir
     upload: Importar
+  in_memoriam_html: En Memòria.
   landing_strip_html: "<strong>%{name}</strong> utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse."
   landing_strip_signup_html: S’es pas lo cas, podètz <a href="%{sign_up_path}">vos marcar aquí</a>.
   media_attachments:
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
index c58c1c2f8..49dace354 100644
--- a/config/locales/pl.yml
+++ b/config/locales/pl.yml
@@ -9,7 +9,7 @@ pl:
     contact_missing: Nie ustawiono
     contact_unavailable: Nie dotyczy
     description_headline: Czym jest %{domain}?
-    domain_count_after: instancji
+    domain_count_after: instancjami
     domain_count_before: Serwer połączony z
     extended_description_html: |
       <h3>Dobre miejsce na zasady użytkowania</h3>
@@ -59,13 +59,19 @@ pl:
       destroyed_msg: Pomyślnie usunięto notatkę moderacyjną!
     accounts:
       are_you_sure: Jesteś tego pewien?
+      by_domain: Domena
       confirm: Potwierdź
       confirmed: Potwierdzono
+      demote: Degraduj
+      disable: Dezaktywuj
       disable_two_factor_authentication: Wyłącz uwierzytelnianie dwuetapowe
+      disabled: Dezaktywowano
       display_name: Wyświetlana nazwa
       domain: Domena
       edit: Edytuj
       email: Adres e-mail
+      enable: Aktywuj
+      enabled: Aktywowano
       feed_url: Adres kanału
       followers: Śledzący
       followers_url: Adres śledzących
@@ -77,7 +83,9 @@ pl:
         local: Lokalne
         remote: Zdalne
         title: Położenie
+      login_status: Stan logowania
       media_attachments: Załączniki multimedialne
+      memorialize: Przełącz na „In Memoriam”
       moderation:
         all: Wszystkie
         silenced: Wyciszone
@@ -94,6 +102,7 @@ pl:
       outbox_url: Adres skrzynki nadawczej
       perform_full_suspension: Całkowicie zawieś
       profile_url: Adres profilu
+      promote: Podnieś uprawnienia
       protocol: Protokół
       public: Publiczne
       push_subscription_expires: Subskrypcja PuSH wygasa
@@ -101,6 +110,11 @@ pl:
       reset: Resetuj
       reset_password: Resetuj hasło
       resubscribe: Ponów subskrypcję
+      role: Uprawnienia
+      roles:
+        admin: Administrator
+        moderator: Moderator
+        user: Użytkownik
       salmon_url: Adres Salmon
       search: Szukaj
       shared_inbox_url: Adres udostępnianej skrzynki
@@ -109,7 +123,7 @@ pl:
         report: zgłoszeń
         targeted_reports: Zgłoszenia dotyczące tego użytkownika
       silence: Wycisz
-      statuses: Statusy
+      statuses: Wpisy
       subscribe: Subskrybuj
       title: Konta
       undo_silenced: Cofnij wyciszenie
@@ -130,12 +144,16 @@ pl:
       enable: Włącz
       enabled_msg: Pomyślnie przywrócono emoji
       image_hint: Plik PNG ważący do 50KB
+      listed: Widoczne
       new:
         title: Dodaj nowe niestandardowe emoji
       shortcode: Shortcode
       shortcode_hint: Co najmniej 2 znaki, tylko znaki alfanumeryczne i podkreślniki
       title: Niestandardowe emoji
-      upload: Wyślij
+      unlisted: Niewidoczne
+      update_failed_msg: Nie udało się zaktualizować emoji
+      updated_msg: Pomyślnie zaktualizowano emoji
+      upload: Dodaj
     domain_blocks:
       add_new: Dodaj nową
       created_msg: Blokada domen jest przetwarzana
@@ -203,7 +221,7 @@ pl:
       reported_by: Zgłaszający
       resolved: Rozwiązane
       silence_account: Wycisz konto
-      status: Status
+      status: Stan
       suspend_account: Zawieś konto
       target: Cel
       title: Zgłoszenia
@@ -212,7 +230,7 @@ pl:
     settings:
       bootstrap_timeline_accounts:
         desc_html: Oddzielaj nazwy użytkowników przecinkami. Działa tylko dla niezablokowanych kont w obrębie instancji. Jeżeli puste, zostaną użyte konta administratorów instancji.
-        title: Domyślne obserwacje nowych użytkowników
+        title: Domyślnie obserwowani użytkownicy
       contact_information:
         email: Służbowy adres e-mail
         username: Nazwa użytkownika do kontaktu
@@ -256,7 +274,7 @@ pl:
         show: Pokaż zawartość multimedialną
         title: Media
       no_media: Bez zawartości multimedialnej
-      title: Statusy konta
+      title: Wpisy konta
       with_media: Z zawartością multimedialną
     subscriptions:
       callback_url: URL zwrotny
@@ -332,7 +350,7 @@ pl:
   errors:
     '403': Nie masz uprawnień, aby wyświetlić tę stronę.
     '404': Strona, którą próbujesz odwiedzić, nie istnieje.
-    '410': Strona, którą próbujesz odwiedzić, już nie istnieje.
+    '410': Strona, którą próbujesz odwiedzić, przestała istnieć.
     '422':
       content: Sprawdzanie bezpieczeństwa nie powiodło się. Czy blokujesz pliki cookie?
       title: Sprawdzanie bezpieczeństwa nie powiodło się
@@ -349,7 +367,7 @@ pl:
     storage: Urządzenie przechowujące dane
   followers:
     domain: Domena
-    explanation_html: Jeżeli chcesz mieć pewność, kto może przeczytać Twoje statusy, musisz kontrolować, kto śledzi Twój profil. <strong>Twoje prywatne statusy są dostarczane na te instancje, na których jesteś śledzony</strong>. Możesz sprawdzać, kto Cię śledzi i blokować ich, jeśli nie ufasz właścicielom lub oprogramowaniu danej instancji.
+    explanation_html: Jeżeli chcesz mieć pewność, kto może przeczytać Twoje wpisy, musisz kontrolować, kto śledzi Twój profil. <strong>Twoje prywatne wpisy są dostarczane na te instancje, na których jesteś śledzony</strong>. Możesz sprawdzać, kto Cię śledzi i blokować ich, jeśli nie ufasz właścicielom lub oprogramowaniu danej instancji.
     followers_count: Liczba śledzących
     lock_link: Zablokuj swoje konto
     purge: Przestań śledzić
@@ -357,7 +375,7 @@ pl:
       one: W trakcie usuwania śledzących z jednej domeny…
       other: W trakcie usuwania śledzących z %{count} domen…
     true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>.
-    unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje statusy. %{lock_link} aby móc kontrolować, kto Cię śledzi.
+    unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi.
     unlocked_warning_title: Twoje konto nie jest zablokowane
   generic:
     changes_saved_msg: Ustawienia zapisane!
@@ -374,11 +392,12 @@ pl:
       following: Lista śledzonych
       muting: Lista wyciszonych
     upload: Załaduj
+  in_memoriam_html: Ku pamięci.
   landing_strip_html: "<strong>%{name}</strong> ma konto na %{link_to_root_path}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersum."
   landing_strip_signup_html: Jeśli jeszcze go nie masz, możesz <a href="%{sign_up_path}">stworzyć konto</a>.
   media_attachments:
     validations:
-      images_and_video: Nie możesz załączyć pliku wideo do statusu, który zawiera już zdjęcia
+      images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia
       too_many: Nie możesz załączyć więcej niż 4 plików
   notification_mailer:
     digest:
@@ -431,7 +450,7 @@ pl:
     web: Sieć
   push_notifications:
     favourite:
-      title: "%{name} dodał Twój status do ulubionych"
+      title: "%{name} dodał Twój wpis do ulubionych"
     follow:
       title: "%{name} zaczął Cię śledzić"
     group:
@@ -442,7 +461,7 @@ pl:
       action_favourite: Dodaj do ulubionych
       title: "%{name} wspomniał o Tobie"
     reblog:
-      title: "%{name} podbił Twój status"
+      title: "%{name} podbił Twój wpis"
   remote_follow:
     acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić
     missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
@@ -610,7 +629,7 @@ pl:
     manual_instructions: 'Jeżeli nie możesz zeskanować kodu QR, musisz wprowadzić ten kod ręcznie:'
     recovery_codes: Przywróć kody zapasowe
     recovery_codes_regenerated: Pomyślnie wygenerowano ponownie kody zapasowe
-    recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. <strong>Trzymaj je w bezpiecznym miejscu</strong>. Na przykład, wydrukuj je i przechowuj z ważnymu dokumentami.
+    recovery_instructions_html: Jeżeli kiedykolwiek utracisz dostęp do telefonu, możesz wykorzystać jeden z kodów zapasowych, aby odzyskać dostęp do konta. <strong>Trzymaj je w bezpiecznym miejscu</strong>. Na przykład, wydrukuj je i przechowuj z ważnymi dokumentami.
     setup: Skonfiguruj
     wrong_code: Wprowadzony kod jest niepoprawny! Czy czas serwera i urządzenia jest poprawny?
   users:
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 4b9ea152f..f5c61c01c 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -43,7 +43,7 @@ pt-BR:
     people_followed_by: Pessoas que %{name} segue
     people_who_follow: Pessoas que seguem %{name}
     posts: Toots
-    posts_with_replies: Toots com respostas
+    posts_with_replies: Toots e respostas
     remote_follow: Siga remotamente
     reserved_username: Este usuário está reservado
     roles:
@@ -59,13 +59,19 @@ pt-BR:
       destroyed_msg: Nota de moderação excluída com sucesso!
     accounts:
       are_you_sure: Você tem certeza?
+      by_domain: Domínio
       confirm: Confirmar
       confirmed: Confirmado
+      demote: Rebaixar
+      disable: Desativar
       disable_two_factor_authentication: Desativar 2FA
+      disabled: Desativado
       display_name: Nome de exibição
       domain: Domínio
       edit: Editar
       email: E-mail
+      enable: Ativar
+      enabled: Ativado
       feed_url: URL do feed
       followers: Seguidores
       followers_url: URL de seguidores
@@ -77,7 +83,9 @@ pt-BR:
         local: Local
         remote: Remoto
         title: Localização
+      login_status: Status de login
       media_attachments: Mídia(s) anexada(s)
+      memorialize: Tornar um memorial
       moderation:
         all: Todos
         silenced: Silenciados
@@ -94,6 +102,7 @@ pt-BR:
       outbox_url: URL da Outbox
       perform_full_suspension: Efetue suspensão total
       profile_url: URL do perfil
+      promote: Promover
       protocol: Protocolo
       public: Público
       push_subscription_expires: Inscrição PuSH expira
@@ -101,6 +110,11 @@ pt-BR:
       reset: Anular
       reset_password: Modificar senha
       resubscribe: Reinscrever-se
+      role: Permissões
+      roles:
+        admin: Administrador
+        moderator: Moderador
+        user: Usuário
       salmon_url: Salmon URL
       search: Pesquisar
       shared_inbox_url: URL da Inbox Compartilhada
@@ -130,11 +144,16 @@ pt-BR:
       enable: Habilitar
       enabled_msg: Emoji habilitado com sucesso!
       image_hint: PNG de até 50KB
+      listed: Listado
       new:
         title: Adicionar novo emoji customizado
+      overwrite: Sobrescrever
       shortcode: Atalho
       shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores
       title: Emojis customizados
+      unlisted: Não listado
+      update_failed_msg: Não foi possível atualizar esse emoji
+      updated_msg: Emoji atualizado com sucesso!
       upload: Enviar
     domain_blocks:
       add_new: Adicionar novo
@@ -373,6 +392,7 @@ pt-BR:
       following: Pessoas que você segue
       muting: Lista de silêncio
     upload: Enviar
+  in_memoriam_html: Em memória de
   landing_strip_html: "<strong>%{name}</strong> é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso."
   landing_strip_signup_html: Se não, você pode <a href="%{sign_up_path}">se cadastrar aqui</a>.
   media_attachments:
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index aafae48ce..faf41f316 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -54,6 +54,7 @@ en:
       interactions:
         must_be_follower: Block notifications from non-followers
         must_be_following: Block notifications from people you don't follow
+        must_be_following_dm: Block direct messages from people you don't follow
       notification_emails:
         digest: Send digest e-mails
         favourite: Send e-mail when someone favourites your status
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
index 993eae706..48eaf79b2 100644
--- a/config/locales/simple_form.ja.yml
+++ b/config/locales/simple_form.ja.yml
@@ -50,6 +50,7 @@ ja:
       interactions:
         must_be_follower: フォロワー以外からの通知をブロック
         must_be_following: フォローしていないユーザーからの通知をブロック
+        must_be_following_dm: フォローしていないユーザーからのダイレクトメッセージをブロック
       notification_emails:
         digest: タイムラインからピックアップしてメールで通知する
         favourite: お気に入りに登録された時にメールで通知する
diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml
index d43c0d7eb..f178d1857 100644
--- a/config/locales/simple_form.oc.yml
+++ b/config/locales/simple_form.oc.yml
@@ -13,7 +13,7 @@ oc:
           one: Demòra encara <span class="name-counter">1</span> caractèr
           other: Demòran encara <span class="name-counter">%{count}</span> caractèrs
         setting_noindex: Aquò es destinat a vòstre perfil public e vòstra pagina d’estatuts
-        setting_theme: Aquò càmbia lo tèma grafic de Mastodon quand sètz connectat qualque siaque lo periferic.
+        setting_theme: Aquò càmbia lo tèma grafic de Mastodon quand sètz connectat qual que siasque lo periferic.
       imports:
         data: Fichièr CSV exportat d’una autra instància Mastodon
       sessions:
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
index 68f84d109..8b539662c 100644
--- a/config/locales/simple_form.pl.yml
+++ b/config/locales/simple_form.pl.yml
@@ -58,6 +58,7 @@ pl:
       interactions:
         must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą
         must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz
+        must_be_following_dm: Nie wyświetlaj wiadomości bezpośrednich od osób, których nie śledzisz
       notification_emails:
         digest: Wysyłaj podsumowania e-mailem
         favourite: Powiadamiaj mnie e-mailem, gdy ktoś polubi mój wpis
diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml
index eafaa972e..23a1f59da 100644
--- a/config/locales/simple_form.zh-CN.yml
+++ b/config/locales/simple_form.zh-CN.yml
@@ -3,49 +3,61 @@ zh-CN:
   simple_form:
     hints:
       defaults:
-        avatar: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 120x120px
-        display_name: 不起过 30 个字符
-        header: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 700x335px
-        locked: 默认仅向粉丝公开嘟文,需要手工批准粉丝关注请求。
-        note: 最多 160 个字符
+        avatar: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 120×120px
+        digest: 在你长时间未登录的情况下,我们会向你发送一份含有提及你的嘟文的摘要邮件
+        display_name: 还能输入 <span class="name-counter">%{count}</span> 个字符
+        header: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 700×335px
+        locked: 你需要手动审核所有关注请求
+        note: 还能输入 <span class="note-counter">%{count}</span> 个字符
+        setting_noindex: 此设置会影响到你的公开个人资料以及嘟文页面
+        setting_theme: 此设置会影响到你从任意设备登录时 Mastodon 的显示样式
       imports:
-        data: 从其他服务器节点导出的 CSV 文件
+        data: 请上传从其他 Mastodon 实例导出的 CSV 文件
       sessions:
-        otp: 输入你手机生成的两步验证码,或者恢复代码。
+        otp: 输入你手机上生成的双重认证码,或者任意一个恢复代码。
       user:
-        filtered_languages: 下列被选择的语言的嘟文将不会出现在你的公共时间轴上。
+        filtered_languages: 勾选语言的嘟文将不会出现在你的公共时间轴上
     labels:
       defaults:
         avatar: 头像
         confirm_new_password: 确认新密码
         confirm_password: 确认密码
         current_password: 当前密码
-        data: 数据
-        display_name: 显示名
-        email: 邮箱
-        filtered_languages: 屏蔽下列语言的嘟文
-        header: 个人页面顶部
+        data: 数据文件
+        display_name: 昵称
+        email: 电子邮件地址
+        filtered_languages: 语言过滤
+        header: 个人资料页横幅图片
         locale: 语言
-        locked: 隐私模式(锁嘟)
+        locked: 保护你的帐户(锁嘟)
         new_password: 新密码
         note: 简介
-        otp_attempt: 两步认证码
+        otp_attempt: 双重认证代码
         password: 密码
+        setting_auto_play_gif: 自动播放 GIF 动画
         setting_boost_modal: 在转嘟前询问我
-        setting_default_privacy: 嘟文默认隐私度
-        severity: 等级
+        setting_default_privacy: 嘟文默认可见范围
+        setting_default_sensitive: 总是将我发送的媒体文件标记为敏感内容
+        setting_delete_modal: 在删除嘟文前询问我
+        setting_noindex: 禁止搜索引擎建立索引
+        setting_reduce_motion: 降低过渡动画效果
+        setting_system_font_ui: 使用系统默认字体
+        setting_theme: 站点主题
+        setting_unfollow_modal: 在取消关注前询问我
+        severity: 级别
         type: 导入数据类型
         username: 用户名
       interactions:
-        must_be_follower: 隐藏没有关注你的用户的通知
-        must_be_following: 隐藏你不关注的用户的通知
+        must_be_follower: 屏蔽来自未关注你的用户的通知
+        must_be_following: 屏蔽来自你未关注的用户的通知
+        must_be_following_dm: 屏蔽来自你未关注的用户的私信
       notification_emails:
         digest: 发送摘要邮件
-        favourite: 当有用户赞了你的嘟文时,发电邮通知
-        follow: 当有用户关注你时,发电邮通知
-        follow_request: 当有用户要求关注你时,发电邮通知
-        mention: 当有用户在嘟文中提及你时,发电邮通知
-        reblog: 当有用户转嘟了你的嘟文时,发电邮通知
+        favourite: 当有用户收藏了你的嘟文时,发送电子邮件提醒我
+        follow: 当有用户关注你时,发送电子邮件提醒我
+        follow_request: 当有用户向你发送关注请求时,发送电子邮件提醒我
+        mention: 当有用户在嘟文中提及你时,发送电子邮件提醒我
+        reblog: 当有用户转嘟了你的嘟文时,发送电子邮件提醒我
     'no': 否
     required:
       mark: "*"
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 44d0f3803..bba70f2e4 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -1,196 +1,351 @@
 ---
 zh-CN:
   about:
-    about_mastodon_html: Mastodon(长毛象)是一个<em>自由、开放源码</em>的社交网站。它是一个分布式的服务,避免你的通信被单一商业机构垄断操控。请你选择一家你信任的 Mastodon 实例,在上面创建帐号,然后你就可以和任一 Mastodon 实例上的用户互通,享受无缝的<em>社交</em>交流。
+    about_hashtag_html: 这里展示的是带有话题标签 <strong>#%{hashtag}</strong> 的公开嘟文。如果你想与他们互动,你需要在任意一个 Mastodon 实例或与其兼容的网站上拥有一个帐户。
+    about_mastodon_html: Mastodon(长毛象)是一个基于开放式网络协议和自由、开源软件建立的社交网络,有着类似于电子邮件的分布式设计。
     about_this: 关于本实例
-    closed_registrations: 这个实例目前不开放注册 _(:3」∠)_
+    closed_registrations: 这个实例目前没有开放注册。不过,你可以前往其他实例注册一个帐户,同样可以加入到这个网络中哦!
     contact: 联络
+    contact_missing: 未设定
+    contact_unavailable: 未公开
     description_headline: 关于 %{domain}
     domain_count_after: 个其它实例
     domain_count_before: 现已接入
-    other_instances: 其它实例
+    extended_description_html: |
+      <h3>这里可以写一些规定</h3>
+      <p>本站尚未设置详细介绍。</p>
+    features:
+      humane_approach_body: Mastodon 从其他网络的失败经验中汲取了教训,致力于在与错误的社交媒体使用方式的斗争中做出符合伦理化设计的选择。
+      humane_approach_title: 更加以人为本
+      not_a_product_body: Mastodon 绝非一个商业网络。这里既没有广告,也没有数据挖掘,更没有围墙花园。中心机构在这里不复存在。
+      not_a_product_title: 作为用户,你并非一件商品
+      real_conversation_body: Mastodon 有着高达 500 字的字数限制,以及对内容的细化控制和媒体警告提示的支持,只为让你能够畅所欲言。
+      real_conversation_title: 为真正的交流而生
+      within_reach_body: 通过一个面向开发者友好的 API 生态系统,Mastodon 让你可以随时随地通过众多 iOS、Android 以及其他平台的应用与朋友们保持联系。
+      within_reach_title: 始终触手可及
+    find_another_instance: 寻找另一个实例
+    generic_description: "%{domain} 是这个庞大网络中的一台服务器"
+    hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例
+    learn_more: 详细了解
+    other_instances: 其他实例
     source_code: 源码
     status_count_after: 条嘟文
     status_count_before: 他们共嘟出了
     user_count_after: 位用户
     user_count_before: 这里共注册有
+    what_is_mastodon: Mastodon 是什么?
   accounts:
     follow: 关注
-    followers: 粉丝
-    following: 关注
-    nothing_here: 神马都没有!
-    people_followed_by: 正关注
-    people_who_follow: 粉丝
+    followers: 关注者
+    following: 正在关注
+    media: 媒体
+    nothing_here: 这里神马都没有!
+    people_followed_by: "%{name} 关注的人"
+    people_who_follow: 关注 %{name} 的人
     posts: 嘟文
+    posts_with_replies: 嘟文和回复
     remote_follow: 跨站关注
+    reserved_username: 此用户名已保留
+    roles:
+      admin: 管理员
+      moderator: 协管
     unfollow: 取消关注
   admin:
+    account_moderation_notes:
+      account: 管理员
+      create: 新建
+      created_at: 日期
+      created_msg: 管理记录建立成功!
+      delete: 删除
+      destroyed_msg: 管理记录删除成功!
     accounts:
       are_you_sure: 你确定吗?
+      by_domain: 域名
       confirm: 确认
       confirmed: 已确认
-      disable_two_factor_authentication: 两步认证无效
-      display_name: 显示名称
+      demote: 降任
+      disable: 停用
+      disable_two_factor_authentication: 停用双重认证
+      disabled: 已停用
+      display_name: 昵称
       domain: 域名
       edit: 编辑
-      email: 电邮地址
+      email: 电子邮件地址
+      enable: 启用
+      enabled: 已启用
       feed_url: 订阅 URL
       followers: 关注者
+      followers_url: 关注者(Followers)URL
       follows: 正在关注
-      ip: IP地址
+      inbox_url: 收件箱(Inbox)URL
+      ip: IP 地址
       location:
         all: 全部
         local: 本地
         remote: 远程
-        title: 地点
+        title: 位置
+      login_status: 登录状态
       media_attachments: 媒体文件
+      memorialize: 设置为追悼帐户
       moderation:
         all: 全部
-        silenced: 被静音的
-        suspended: 被停权的
-        title: 管理操作
-      most_recent_activity: 最新活动
-      most_recent_ip: 最新 IP 地址
+        silenced: 已隐藏
+        suspended: 已封禁
+        title: 帐户状态
+      moderation_notes: 管理记录
+      most_recent_activity: 最后一次活跃的时间
+      most_recent_ip: 最后一次活跃的 IP 地址
       not_subscribed: 未订阅
       order:
         alphabetic: 按字母
         most_recent: 按时间
         title: 排序
-      perform_full_suspension: 实行完全暂停
-      profile_url: 个人文件 URL
-      public: 公共
-      push_subscription_expires: 推送订阅过期
+      outbox_url: 发件箱(Outbox)URL
+      perform_full_suspension: 永久封禁
+      profile_url: 个人资料页面 URL
+      promote: 升任
+      protocol: 协议
+      public: 公开页面
+      push_subscription_expires: PuSH 订阅过期时间
+      redownload: 刷新头像
       reset: 重置
       reset_password: 重置密码
-      salmon_url: Salmon 反馈 URL
+      resubscribe: 重新订阅
+      role: 用户组
+      roles:
+        admin: 管理员
+        moderator: 协管
+        user: 用户
+      salmon_url: Salmon URL
       search: 搜索
+      shared_inbox_url: 公用收件箱(Shared Inbox)URL
       show:
-        created_reports: 这个帐户创建的报告
-        report: 报告
-        targeted_reports: 关于这个帐户的报告
-      silence: 静音
+        created_reports: 这个帐户提交的举报
+        report: 个举报
+        targeted_reports: 针对这个帐户的举报
+      silence: 隐藏
       statuses: 嘟文
+      subscribe: 订阅
       title: 用户
-      undo_silenced: 解除静音
-      undo_suspension: 解除停权
-      username: 用户名称
-      web: 用户页面
+      undo_silenced: 解除隐藏
+      undo_suspension: 解除封禁
+      unsubscribe: 取消订阅
+      username: 用户名
+      web: 站内页面
+    custom_emojis:
+      copied_msg: 成功将表情复制到本地
+      copy: 复制
+      copy_failed_msg: 无法将表情复制到本地
+      created_msg: 表情添加成功!
+      delete: 删除
+      destroyed_msg: 表情删除成功!
+      disable: 停用
+      disabled_msg: 表情停用成功
+      emoji: 表情
+      enable: 启用
+      enabled_msg: 表情启用成功
+      image_hint: PNG 格式,最大 50KB
+      listed: 已显示
+      new:
+        title: 添加新的自定义表情
+      overwrite: 覆盖
+      shortcode: 短代码
+      shortcode_hint: 至少 2 个字符,只能使用字母、数字和下划线
+      title: 自定义表情
+      unlisted: 已隐藏
+      update_failed_msg: 表情更新失败!
+      updated_msg: 表情更新成功!
+      upload: 上传
     domain_blocks:
-      add_new: 添加
-      created_msg: 正处理域名阻隔
-      destroyed_msg: 已撤销域名阻隔
-      domain: 域名阻隔
+      add_new: 添加新条目
+      created_msg: 正在进行域名屏蔽
+      destroyed_msg: 域名屏蔽已撤销
+      domain: 域名
       new:
-        create: 添加域名阻隔
-        hint: "「域名阻隔」不会隔绝该域名用户的嘟帐户入本站数据库,但会嘟文抵达后,自动套用特定的审批操作。"
+        create: 添加域名屏蔽
+        hint: 域名屏蔽不会阻止该域名下的帐户进入本站的数据库,但是会对来自这个域名的帐户自动进行预先设置的管理操作。
         severity:
-          desc_html: "「<strong>自动静音</strong>」令该域名用户的嘟文,设为只对关注者显示,没有关注的人会看不到。 「<strong>自动除名</strong>」会自动将该域名用户的嘟文、媒体文件、个人资料从本服务器实例删除。"
-          silence: 自动静音
-          suspend: 自动除名
-        title: 添加域名阻隔
-      reject_media: 拒绝媒体文件
-      reject_media_hint: 删除本地缓存的媒体文件,再也不在未来下载这个站点的文件。和自动除名无关。
+          desc_html: 选择<strong>自动隐藏</strong>会将该域名下帐户发送的嘟文设置为仅关注者可见;选择<strong>自动封禁</strong>会将该域名下帐户发送的嘟文、媒体文件以及个人资料数据从本实例上删除;如果你只是想拒绝接收来自该域名的任何媒体文件,请选择<strong>无</strong>。
+          noop: 无
+          silence: 自动隐藏
+          suspend: 自动封禁
+        title: 添加域名屏蔽
+      reject_media: 拒绝接收媒体文件
+      reject_media_hint: 删除本地已缓存的媒体文件,并且不再接收来自该域名的任何媒体文件。此选项不影响封禁
       severities:
-        silence: 自动静音
-        suspend: 自动除名
-      severity: 阻隔程度
+        noop: 无
+        silence: 自动隐藏
+        suspend: 自动封禁
+      severity: 屏蔽级别
       show:
-        affected_accounts:
-          one: 数据库中有1个帐户受影响
-          other: 数据库中有%{count}个帐户受影响
+        affected_accounts: 将会影响到数据库中的 %{count} 个帐户
         retroactive:
-          silence: 对此域名的所有帐户取消静音
-          suspend: 对此域名的所有帐户取消除名
-        title: 撤销 %{domain} 的域名阻隔
+          silence: 对此域名的所有帐户解除隐藏
+          suspend: 对此域名的所有帐户解除封禁
+        title: 撤销对 %{domain} 的域名屏蔽
         undo: 撤销
-      title: 域名阻隔
+      title: 域名屏蔽
       undo: 撤销
+    email_domain_blocks:
+      add_new: 添加新条目
+      created_msg: 电子邮件域名屏蔽添加成功
+      delete: 删除
+      destroyed_msg: 电子邮件域名屏蔽删除成功
+      domain: 域名
+      new:
+        create: 添加屏蔽
+        title: 添加电子邮件域名屏蔽
+      title: 电子邮件域名屏蔽
     instances:
-      account_count: 已知帐号
+      account_count: 已知帐户
       domain_name: 域名
+      reset: 重置
+      search: 搜索
       title: 已知实例
     reports:
-      are_you_sure: 你确定吗?
+      action_taken_by: 操作执行者
+      are_you_sure: 你确定吗?
       comment:
         label: 备注
         none: 没有
       delete: 删除
       id: ID
-      mark_as_resolved: 标示为「已处理」
+      mark_as_resolved: 标记为“已处理”
       nsfw:
-        'false': NSFW无效
-        'true': NSFW有效
+        'false': 取消 NSFW 标记
+        'true': 添加 NSFW 标记
       report: '举报 #%{id}'
+      report_contents: 内容
       reported_account: 举报用户
-      reported_by: 举报者
+      reported_by: 举报人
       resolved: 已处理
-      silence_account: 将用户静音
+      silence_account: 隐藏用户
       status: 状态
-      suspend_account: 将用户停权
-      target: 对象
+      suspend_account: 封禁用户
+      target: 被举报人
       title: 举报
       unresolved: 未处理
       view: 查看
     settings:
+      bootstrap_timeline_accounts:
+        desc_html: 用半角逗号分隔多个用户名。只能添加来自本站且未开启保护的帐户。如果留空,则默认关注本站所有的管理员。
+        title: 新用户默认关注
       contact_information:
-        email: 输入一个公开的电邮地址
-        username: 输入用户名称
+        email: 输入一个公开的电子邮件地址
+        username: 输入用户名
       registrations:
         closed_message:
-          desc_html: 当本站暂停接受注册时,会显示这个消息。<br/> 可使用 HTML
-          title: 暂停注册消息
+          desc_html: 本站关闭注册期间的提示信息。可以使用 HTML 标签
+          title: 关闭注册时的提示消息
+        deletion:
+          desc_html: 允许所有人删除自己的帐户
+          title: 开放删除帐户权限
         open:
+          desc_html: 允许任何人建立一个帐户
           title: 开放注册
       site_description:
-        desc_html: 在首页显示,及在 meta 标签中用作网站介绍。<br>你可以在此使用 HTML 标签,尤其是<code>&lt;a&gt;</code> 和 <code>&lt;em&gt;</code>。
-        title: 本站介绍
+        desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <code>&lt;a&gt;</code> 和 <code>&lt;em&gt;</code>。
+        title: 本站简介
       site_description_extended:
-        desc_html: 本站详细信息页的內容<br/>你可在此使用 HTML
-        title: 本站详细信息
+        desc_html: 可以填写行为守则、规定、指南或其他本站特有的内容。可以使用 HTML 标签
+        title: 本站详细介绍
+      site_terms:
+        desc_html: 可以填写自己的隐私权政策、使用条款或其他法律文本。可以使用 HTML 标签
+        title: 自定义使用条款
       site_title: 本站名称
+      thumbnail:
+        desc_html: 用于在 OpenGraph 和 API 中显示预览图。推荐分辨率 1200×630px
+        title: 本站缩略图
+      timeline_preview:
+        desc_html: 在主页显示公开时间线
+        title: 时间线预览
       title: 网站设置
+    statuses:
+      back_to_account: 返回帐户信息页
+      batch:
+        delete: 删除
+        nsfw_off: 取消 NSFW 标记
+        nsfw_on: 添加 NSFW 标记
+      execute: 执行
+      failed_to_execute: 执行失败
+      media:
+        hide: 隐藏媒体文件
+        show: 显示媒体文件
+        title: 媒体文件
+      no_media: 不含媒体文件
+      title: 帐户嘟文
+      with_media: 含有媒体文件
     subscriptions:
       callback_url: 回调 URL
-      confirmed: 确定
-      expires_in: 期限
-      last_delivery: 数据最后送抵时间
-      title: WebSub 订阅
-      topic: 所订阅资源
+      confirmed: 已确认
+      expires_in: 失效时间
+      last_delivery: 最后一次接收数据的时间
+      title: WebSub
+      topic: Topic
     title: 管理
+  admin_mailer:
+    new_report:
+      body: "%{reporter} 举报了 %{target}"
+      subject: 来自 %{instance} 的新举报(#%{id})
   application_mailer:
-    settings: 更改电邮设置︰%{link}
+    salutation: "%{name},"
+    settings: 更改电子邮件首选项:%{link}
     signature: 来自 %{instance} 的 Mastodon 通知
     view: 查看:
   applications:
+    created: 应用创建成功
+    destroyed: 应用删除成功
     invalid_url: URL 无效
+    regenerate_token: 重置访问令牌
+    token_regenerated: 访问令牌重置成功
+    warning: 一定小心,千万不要把它分享给任何人!
+    your_token: 你的访问令牌
   auth:
-    change_password: 登录凭据
+    agreement_html: 注册即表示你同意<a href="%{rules_path}">我们的使用条款</a>和<a href="%{terms_path}">隐私权政策</a>。
+    change_password: 帐户安全
+    delete_account: 删除帐户
+    delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
     didnt_get_confirmation: 没有收到确认邮件?
     forgot_password: 忘记密码?
+    invalid_reset_password_token: 密码重置令牌无效或已过期。请重新发起重置密码请求。
     login: 登录
     logout: 登出
     register: 注册
-    resend_confirmation: 重发确认邮件
+    resend_confirmation: 重新发送确认邮件
     reset_password: 重置密码
     set_new_password: 设置新密码
   authorize_follow:
     error: 对不起,寻找这个跨站用户时出错
     follow: 关注
+    follow_request: 关注请求已发送给:
+    following: 成功!你正在关注:
+    post_follow:
+      close: 你也可以直接关闭这个窗口。
+      return: 返回至个人资料页
+      web: 返回本站
     title: 关注 %{acct}
   datetime:
     distance_in_words:
-      about_x_hours: "%{count} 小时"
+      about_x_hours: "%{count} 时"
       about_x_months: "%{count} 个月"
       about_x_years: "%{count} 年"
-      almost_x_years: 接近 %{count} 年
+      almost_x_years: "%{count} 年"
       half_a_minute: 刚刚
-      less_than_x_minutes: "%{count} 分不到"
+      less_than_x_minutes: "%{count} 分"
       less_than_x_seconds: 刚刚
-      over_x_years: 超过 %{count} 年
+      over_x_years: "%{count} 年"
       x_days: "%{count} 天"
       x_minutes: "%{count} 分"
       x_months: "%{count} 个月"
       x_seconds: "%{count} 秒"
+  deletes:
+    bad_password_msg: 想得美,黑客!密码输入错误
+    confirm_password: 输入你当前的密码来验证身份
+    description_html: 继续操作将会<strong>永久地、不可撤销地</strong>删除你帐户中的内容,并冻结你的帐户。你的用户名将会被保留,以防有人冒用你的身份。
+    proceed: 删除帐户
+    success_msg: 你的帐户已经成功删除
+    warning_html: 我们只能保证本实例上的内容已经被彻底删除。对于已经被广泛传播的内容,它们在本实例以外的某些地方可能仍然可见。此外,失去连接的服务器以及停止接收订阅的服务器上的数据亦无法删除。
+    warning_title: 关于已传播的内容的警告
   errors:
     '403': 无权查看
     '404': 找不到页面
@@ -199,68 +354,71 @@ zh-CN:
       content: 无法确认登录信息。你是不是屏蔽了 Cookie?
       title: 无法确认登录信息
     '429': 被限制
+    '500':
+      content: 抱歉,我们这里出错了。
+      title: 这个页面不正确
+    noscript_html: 请启用 JavaScript 以便使用 Mastodon 网页版应用。你也可以选择适用于你的平台的 <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Mastodon 应用</a>。
   exports:
-    blocks: 被你封锁的用户
+    blocks: 屏蔽的用户
     csv: CSV
-    follows: 你所关注的用户
-    mutes: 你所静音的用户
-    storage: 媒体容量大小
+    follows: 关注的用户
+    mutes: 隐藏的用户
+    storage: 媒体文件存储
   followers:
     domain: 域名
-    explanation_html: 想要保护你的嘟文的话,请慎重考虑关注你的人。<strong>你的受保护的嘟文会发送到有你的关注者的所有实例上</strong>。你也许想要复查一下关注者列表来移除那些你无法信任的关注者。
+    explanation_html: 为保证你的嘟文的隐私安全,你应当时刻留意你的关注者列表。<strong>受保护的嘟文将会发送到所有关注者所在的实例上</strong>。有些实例使用的软件代码或其管理员可能不会尊重你的隐私设置,因此你应当复查一下关注者列表,并移除那些你无法信任的关注者。
     followers_count: 关注者数量
-    lock_link: 保护你的帐户
+    lock_link: 为你的帐户开启保护
     purge: 从关注者中移除
-    success: 从 %{count} 个域名中移除了关注者。
-    true_privacy_html: "<strong>真正的隐私只能靠端到端加密来实现</strong>!"
-    unlocked_warning_html: 任何人都可以关注你然后查看被保护的嘟文, %{lock_link} 可以复核和拒绝关注请求。
-    unlocked_warning_title: 你的帐户没被保护
+    success: 正在从 %{count} 个域名中移除关注者……
+    true_privacy_html: 请始终铭记:<strong>真正的隐私只能靠端到端加密来实现</strong>!
+    unlocked_warning_html: 任何人都可以通过关注你来立即查看被保护的嘟文。%{lock_link},即可审核并拒绝关注请求。
+    unlocked_warning_title: 你的帐户未受到保护
   generic:
-    changes_saved_msg: 更改已被保存。
+    changes_saved_msg: 更改保存成功!
     powered_by: 基于 %{link} 构建
-    save_changes: 保存
+    save_changes: 保存更改
     validation_errors:
-      one: 出错啦!请确认以下出错的地方,修改之后再来一次:
-      other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次:
+      one: 出错啦!检查一下下面出错的地方吧
+      other: 出错啦!检查一下下面 %{count} 处出错的地方吧
   imports:
-    preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。
-    success: 你已成功上载数据文件,我们正将数据导入,请稍候
+    preface: 你可以在此导入你在其他实例导出的数据,比如你所关注或屏蔽的用户列表。
+    success: 数据上传成功,正在处理中
     types:
-      blocking: 封锁名单
-      following: 关注名单
-      muting: 静音名单
-    upload: 上载
-  landing_strip_html: "<strong>%{name}</strong> 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里(Mastodon、GNU social)任一服务器实例的用户,便可以跨站关注此站用户并与其沟通。"
-  landing_strip_signup_html: 如果你没有这类帐户,欢迎在<a href="%{sign_up_path}">此处登记</a>。
+      blocking: 屏蔽列表
+      following: 关注列表
+      muting: 隐藏列表
+    upload: 上传
+  in_memoriam_html: 谨此悼念。
+  landing_strip_html: "<strong>%{name}</strong> 是一位来自 %{link_to_root_path} 的用户。如果你想关注他们或者与他们互动,你需要在任意一个 Mastodon 实例或与其兼容的网站上拥有一个帐户。"
+  landing_strip_signup_html: 还没有这种帐户?你可以<a href="%{sign_up_path}">在本站注册一个</a>。
   media_attachments:
     validations:
-      images_and_video: 无法添加视频到一个已经包含图片的嘟文中
-      too_many: 最多只能添加4张图片
+      images_and_video: 无法在嘟文中同时插入视频和图片
+      too_many: 最多只能添加 4 张图片
   notification_mailer:
     digest:
-      body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴:
-      mention: "%{name} 在此提及了你︰"
+      body: 自从你最后一次(时间是%{since})登录 %{instance} 以来,你错过了这些嘟嘟滴滴:
+      mention: "%{name} 在嘟文中提到了你:"
       new_followers_summary:
-        one: 有人关注你了!耶!
-        other: 有 %{count} 个人关注了你!别激动!
-      subject:
-        one: "你有一个新通知 \U0001F418"
-        other: "%{count} 个通知太多,赶快去看看 \U0001F418"
+        one: 有个人关注了你!耶!
+        other: 有 %{count} 个人关注了你!好棒!
+      subject: "自从你最后一次登录以来,你错过了 %{count} 条新通知 \U0001F418"
     favourite:
-      body: "%{name} 收藏了你"
-      subject: "%{name} 给你点了收藏"
+      body: 你的嘟文被 %{name} 收藏了:
+      subject: "%{name} 收藏了你的嘟文"
     follow:
-      body: "%{name} 关注了你"
+      body: "%{name} 关注了你!"
       subject: "%{name} 关注了你"
     follow_request:
-      body: "%{name} 要求关注你"
-      subject: 等候关注你的用户︰ %{name}
+      body: "%{name} 请求关注你"
+      subject: 待审核的关注者:%{name}
     mention:
-      body: "%{name} 在文章中提及你︰"
-      subject: "%{name} 在文章中提及你"
+      body: "%{name} 在嘟文中提到了你:"
+      subject: "%{name} 提到了你"
     reblog:
-      body: 你的嘟文得到 %{name} 的转嘟
-      subject: "%{name} 转嘟(嘟嘟滴)了你的嘟文"
+      body: 你的嘟文被 %{name} 转嘟了:
+      subject: "%{name} 转嘟了你的嘟文"
   number:
     human:
       decimal_units:
@@ -275,54 +433,197 @@ zh-CN:
   pagination:
     next: 下一页
     prev: 上一页
-    truncate: "……"
+    truncate: "…"
+  preferences:
+    languages: 语言
+    other: 其他
+    publishing: 发布
+    web: 站内
+  push_notifications:
+    favourite:
+      title: "%{name} 收藏了你的嘟文"
+    follow:
+      title: "%{name} 关注了你"
+    group:
+      title: "%{count} 条新通知"
+    mention:
+      action_boost: 转嘟
+      action_expand: 显示更多
+      action_favourite: 收藏
+      title: "%{name} 提到了你"
+    reblog:
+      title: "%{name} 转嘟了你的嘟文"
   remote_follow:
-    acct: 请输入你的︰用户名称@实例域名
-    missing_resource: 无法找到您的帐户转接网址
-    proceed: 下一步
-    prompt: 你正准备关注︰
+    acct: 请输入你的“用户名@实例域名”
+    missing_resource: 无法确定你的帐户的跳转 URL
+    proceed: 确认关注
+    prompt: 你正准备关注:
+  sessions:
+    activity: 最后一次活跃的时间
+    browser: 浏览器
+    browsers:
+      alipay: 支付宝
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      firefox: Firefox
+      generic: 未知浏览器
+      ie: Internet Explorer
+      micro_messenger: 微信
+      nokia: Nokia S40 Ovi 浏览器
+      opera: Opera
+      phantom_js: PhantomJS
+      qq: QQ浏览器
+      safari: Safari
+      uc_browser: UC浏览器
+      weibo: 新浪微博
+    current_session: 当前会话
+    description: "%{platform} 上的 %{browser}"
+    explanation: 你的 Mastodon 帐户目前已在这些浏览器上登录。
+    ip: IP 地址
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: 未知平台
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: 注销
+    revoke_success: 会话注销成功
+    title: 会话
   settings:
     authorized_apps: 已授权的应用
     back: 回到 Mastodon
+    delete: 删除帐户
+    development: 开发
     edit_profile: 更改个人信息
     export: 导出
     followers: 授权的关注者
     import: 导入
+    notifications: 通知
     preferences: 首选项
     settings: 设置
-    two_factor_authentication: 两步认证
+    two_factor_authentication: 双重认证
+    your_apps: 你的应用
   statuses:
-    open_in_web: 打开网页
+    open_in_web: 在站内打开
     over_character_limit: 超过了 %{max} 字的限制
+    pin_errors:
+      limit: 置顶的嘟文条数超出限制
+      ownership: 不能置顶他人的嘟文
+      private: 不能置顶非公开的嘟文
+      reblog: 不能置顶转嘟
     show_more: 显示更多
     visibilities:
-      private: 限关注者
-      private_long: 仅向关注者公开
+      private: 仅关注者
+      private_long: 只有关注你的用户能看到
       public: 公开
-      public_long: 向所有人公开
-      unlisted: 于公共时间线中隐藏
-      unlisted_long: 公开,但不显示在公共时间线中
+      public_long: 所有人可见,并会出现在公共时间轴上
+      unlisted: 不公开
+      unlisted_long: 所有人可见,但不会出现在公共时间轴上
   stream_entries:
-    click_to_show: 显示
+    click_to_show: 点击显示
+    pinned: 置顶嘟文
     reblogged: 转嘟
     sensitive_content: 敏感内容
+  terms:
+    body_html: |
+      <h2>隐私权政策</h2>
+
+      <h3 id="collect">我们收集什么信息?</h3>
+
+      <p>我们从你在我们站点注册开始从你那开始收集信息,并收集关于你在论坛的阅读和写作的数据,并评估分享的内容。</p>
+
+      <p>当在我们站点注册时,你可能被要求输入你的名字和邮件地址。然而你可以在不用注册的情况下访问站点。你的邮件地将通过一个独一无二的链接验证。如果链接被访问了,我们就知道你控制了该邮件地址。</p>
+
+      <p>当已注册和发帖时,我们记录发布帖子时的 IP 地址。我们也可能保留服务器日志,其中包括了每一个向我们服务器的请求。</p>
+
+      <h3 id="use">我们如何使用你的信息?</h3>
+
+      <p>从你那收集的任何数据将以以下方式使用:</p>
+
+      <ul>
+        <li>改进你的个人体验 &mdash; 你的信息帮助我们更好地满足你的个人需求。</li>
+        <li>改进我们的站点 &mdash; 我们基于信息和我们从你那收到的反馈不断地试图改进我们的站点。</li>
+        <li>改善我们的客户服务 &mdash; 你的信息帮助我们更有效地回应用户服务请求和支持。</li>
+        <li>用于发送阶段性的邮件 &mdash; 你提供的邮件地址可能用于接受信息、你想看到的通知或与你用户名有关的回复和询问,或是其他的请求和问题。</li>
+      </ul>
+
+      <h3 id="protect">我们如何保护你的信息?</h3>
+
+      <p>我们实现了一系列的安全措施保证你输入、提交或者访问你个人信息的数据安全。</p>
+
+      <h3 id="data-retention">数据保存政策是什么?</h3>
+
+      <p>我们将善意地:</p>
+
+      <ul>
+        <li>保存 90 天内的所有向服务器的包含 IP 地址的请求。</li>
+        <li>保存 5 年内已注册用户和与他们的帖子有关的 IP 地址。</li>
+      </ul>
+
+      <h3 id="cookies">我们使用 Cookie 吗?</h3>
+
+      <p>是的。Cookie 是网站或它的服务商通过网页浏览器存储在你电脑硬盘上的小文件(如果你同意)。这些 Cookie 使站点能分辨你的浏览器,并且,如果你注册了一个账户,与你的注册账户关联。</p>
+
+      <p>我们使用 Cookie 为之后的访问和编译一小段关于站点流量和交互的数据来判断并保存你的个人设置,这样我们可以在之后提供更好的站点体验和工具。我们可能使用第三方服务商来帮助我们更好地理解我们的站点访客。这些服务商是不允许代表我们使用收集的信息,除非是在帮助我们执行和改进我们的站点。</p>
+
+      <h3 id="disclose">我们会在站外提供任何信息吗?</h3>
+
+      <p>我们绝不销售、交易或任何向外转移你个人信息的行为。这不包括帮助我们管理站点、改进站点或给你提供服务的第三方团体,这些团体需要保证对这些信息保密。当我们认为提交你的信息符合法律、我们的站点政策或保护我们或其他人的权利、知识产权或安全时,我们也可能提交你的信息。然而,非个人访问信息可能供其他团体使用,用于市场、广告或其他用途。</p>
+
+      <h3 id="third-party">第三方链接</h3>
+
+      <p>偶尔地,根据我们的判断,我们可能在我们的站点上包括或提供第三方团体的产品或服务。这些第三方站点用于独立和不同的隐私政策。因此我们对链接到的站点或活动没有责任和权利。尽管如此,我们寻求保护我们的整个站点并且欢迎你给这些站点反馈。</p>
+
+      <h3 id="coppa">儿童在线隐私保护法案合规</h3>
+
+      <p>我们的站点、产品和服务提供给 13 岁以上的人们。如果服务器位于美国,并且你小于 13 岁,根据<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">儿童在线隐私保护法案合规</a>,不要使用这个站点。</p>
+
+      <h3 id="online">仅用于在线隐私政策</h3>
+
+      <p>这个线上隐私政策只适用于通过我们站点收集到的信息,并不包括线下收集的信息。</p>
+
+      <h3 id="consent">你的同意</h3>
+
+      <p>你使用站点的同时,代表你同意了我们网站的隐私政策。</p>
+
+      <h3 id="changes">隐私政策的更改</h3>
+
+      <p>如果我们决定更改我们的隐私政策,我们将在此页更新这些改变。</p>
+
+      <p>文档以 CC-BY-SA 发布。最后更新时间为2013年5月31日。</p>
+
+      <p>原文出自 <a href="https://github.com/discourse/discourse">Discourse 隐私权政策</a>。</p>
+    title: "%{instance} 使用条款和隐私权政策"
+  themes:
+    default: Mastodon
   time:
     formats:
       default: "%Y年%-m月%d日 %H:%M"
   two_factor_authentication:
-    code_hint: 请输入你认证器产生的代码,以确认设置
-    description_html: 当你启用<strong>两步认证</strong>后,你登录时将额外需要使用手机或其他认证器生成的代码。
+    code_hint: 输入你的认证器生成的代码以确认
+    description_html: 启用<strong>双重认证</strong>后,你需要输入手机认证器生成的代码才能登录
     disable: 停用
     enable: 启用
-    enabled_success: 已成功启用两步认证
+    enabled: 双重认证已启用
+    enabled_success: 双重认证启用成功
     generate_recovery_codes: 生成恢复代码
-    instructions_html: "<strong>请用你手机的认证器应用(如 Google Authenticator、Authy),扫描这里的 QR 二维码</strong>。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。"
-    lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的帐户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。<del>(废话)</del>
-    manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰
-    recovery_codes_regenerated: 已成功重新生成恢复代码
-    recovery_instructions_html: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的帐号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。
+    instructions_html: "<strong>请使用 Google 身份验证器或其他 TOTP 双重认证手机应用扫描此处的二维码</strong>。启用双重认证后,你需要输入该应用生成的代码来登录你的帐户。"
+    lost_recovery_codes: 如果你的手机不慎丢失,你可以使用恢复代码来重新获得对帐户的访问权。如果你遗失了恢复代码,可以在此处重新生成。之前使用的恢复代码将会失效。
+    manual_instructions: 如果你无法扫描二维码,请手动输入下列文本:
+    recovery_codes: 备份恢复代码
+    recovery_codes_regenerated: 恢复代码重新生成成功
+    recovery_instructions_html: 如果你的手机无法使用,你可以使用下列任意一个恢复代码来重新获得对帐户的访问权。<strong>请妥善保管好你的恢复代码</strong>(例如,你可以将它们打印出来,然后和其他重要的文件放在一起)。
     setup: 设置
-    wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。
+    wrong_code: 输入的认证码无效!请检查你设备上显示的时间是否正确,如果正确,你可能需要联系管理员以检查服务器的时间是否正确。
   users:
-    invalid_email: 邮箱格式有误
-    invalid_otp_token: 两步认证码有误
+    invalid_email: 输入的电子邮件地址无效
+    invalid_otp_token: 输入的双重认证代码无效
+    signed_in_as: 当前登录的帐户:
diff --git a/config/navigation.rb b/config/navigation.rb
index 9fa029b72..16c48849b 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -21,16 +21,16 @@ SimpleNavigation::Configuration.run do |navigation|
       development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications}
     end
 
-    primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin|
+    primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin|
       admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
       admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts}
-      admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}
-      admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url
-      admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
-      admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}
-      admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }
-      admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }
-      admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url
+      admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? }
+      admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? }
+      admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? }
+      admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? }
+      admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
+      admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }
+      admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }
       admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
     end
 
diff --git a/config/routes.rb b/config/routes.rb
index aca613ed2..7812e4b36 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -133,7 +133,10 @@ Rails.application.routes.draw do
       member do
         post :subscribe
         post :unsubscribe
+        post :enable
+        post :disable
         post :redownload
+        post :memorialize
       end
 
       resource :reset, only: [:create]
@@ -141,6 +144,13 @@ Rails.application.routes.draw do
       resource :suspension, only: [:create, :destroy]
       resource :confirmation, only: [:create]
       resources :statuses, only: [:index, :create, :update, :destroy]
+
+      resource :role do
+        member do
+          post :promote
+          post :demote
+        end
+      end
     end
 
     resources :users, only: [] do
@@ -158,7 +168,13 @@ Rails.application.routes.draw do
     resources :account_moderation_notes, only: [:create, :destroy]
   end
 
-  get '/admin', to: redirect('/admin/settings/edit', status: 302)
+  authenticate :user, lambda { |u| u.admin? } do
+    get '/admin', to: redirect('/admin/settings/edit', status: 302)
+  end
+
+  authenticate :user, lambda { |u| u.moderator? } do
+    get '/admin', to: redirect('/admin/reports', status: 302)
+  end
 
   namespace :api do
     # PubSubHubbub outgoing subscriptions
@@ -204,6 +220,7 @@ Rails.application.routes.draw do
         resource :home, only: :show, controller: :home
         resource :public, only: :show, controller: :public
         resources :tag, only: :show
+        resources :list, only: :show
       end
 
       resources :streaming, only: [:index]
@@ -267,6 +284,10 @@ Rails.application.routes.draw do
           post :unmute
         end
       end
+
+      resources :lists, only: [:index, :create, :show, :update, :destroy] do
+        resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts'
+      end
     end
 
     namespace :web do
diff --git a/config/settings.yml b/config/settings.yml
index c03d0b766..1b8a6673e 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -36,6 +36,7 @@ defaults: &defaults
   interactions:
     must_be_follower: false
     must_be_following: false
+    must_be_following_dm: false
   reserved_usernames:
     - admin
     - support
diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js
index 606eb97f1..74f75d89b 100644
--- a/config/webpack/configuration.js
+++ b/config/webpack/configuration.js
@@ -1,6 +1,6 @@
 // Common configuration for webpacker loaded from config/webpacker.yml
 
-const { dirname, join, resolve } = require('path');
+const { basename, dirname, join, resolve } = require('path');
 const { env } = require('process');
 const { safeLoad } = require('js-yaml');
 const { readFileSync } = require('fs');
@@ -18,8 +18,8 @@ for (let i = 0; i < themeFiles.length; i++) {
   if (!data.pack_directory) {
     data.pack_directory = dirname(themeFile);
   }
-  if (data.name && data.pack) {
-    themes[data.name] = data;
+  if (data.pack) {
+    themes[basename(dirname(themeFile))] = data;
   }
 }
 
diff --git a/config/webpack/production.js b/config/webpack/production.js
index cd1dd91dc..e2d7f11dc 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -9,6 +9,16 @@ const OfflinePlugin = require('offline-plugin');
 const { publicPath } = require('./configuration.js');
 const path = require('path');
 
+let compressionAlgorithm;
+try {
+  const zopfli = require('node-zopfli');
+  compressionAlgorithm = (content, options, fn) => {
+    zopfli.gzip(content, options, fn);
+  };
+} catch (error) {
+  compressionAlgorithm = 'gzip';
+}
+
 module.exports = merge(sharedConfig, {
   output: {
     filename: '[name]-[chunkhash].js',
@@ -33,7 +43,7 @@ module.exports = merge(sharedConfig, {
     }),
     new CompressionPlugin({
       asset: '[path].gz[query]',
-      algorithm: 'gzip',
+      algorithm: compressionAlgorithm,
       test: /\.(js|css|html|json|ico|svg|eot|otf|ttf)$/,
     }),
     new BundleAnalyzerPlugin({ // generates report.html and stats.json
@@ -48,7 +58,37 @@ module.exports = merge(sharedConfig, {
     }),
     new OfflinePlugin({
       publicPath: publicPath, // sw.js must be served from the root to avoid scope issues
-      caches: { }, // do not cache things, we only use it for push notifications for now
+      caches: {
+        main: [':rest:'],
+        additional: [':externals:'],
+        optional: [
+          '**/locale_*.js', // don't fetch every locale; the user only needs one
+          '**/*_polyfills-*.js', // the user may not need polyfills
+          '**/*.woff2', // the user may have system-fonts enabled
+          // images/audio can be cached on-demand
+          '**/*.png',
+          '**/*.jpg',
+          '**/*.jpeg',
+          '**/*.svg',
+          '**/*.mp3',
+          '**/*.ogg',
+        ],
+      },
+      externals: [
+        '/emoji/1f602.svg', // used for emoji picker dropdown
+        '/emoji/sheet.png', // used in emoji-mart
+      ],
+      excludes: [
+        '**/*.gz',
+        '**/*.map',
+        'stats.json',
+        'report.html',
+        // any browser that supports ServiceWorker will support woff2
+        '**/*.eot',
+        '**/*.ttf',
+        '**/*-webfont-*.svg',
+        '**/*.woff',
+      ],
       ServiceWorker: {
         entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'),
         cacheName: 'mastodon',
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index e3a1fc379..5d176db4e 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -12,29 +12,27 @@ const localePackPaths = require('./generateLocalePacks');
 const extensionGlob = `**/*{${settings.extensions.join(',')}}*`;
 const entryPath = join(settings.source_path, settings.source_entry_path);
 const packPaths = sync(join(entryPath, extensionGlob));
-const entryPacks = [...packPaths, ...localePackPaths].filter(path => path !== join(entryPath, 'custom.js'));
-
-const themePaths = Object.keys(themes).reduce(
-  (themePaths, name) => {
-    const themeData = themes[name];
-    themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack);
-    return themePaths;
-  }, {}
-);
 
 module.exports = {
   entry: Object.assign(
-    entryPacks.reduce(
-      (map, entry) => {
-        const localMap = map;
-        let namespace = relative(join(entryPath), dirname(entry));
-        if (namespace === join('..', '..', '..', 'tmp', 'packs')) {
-          namespace = ''; // generated by generateLocalePacks.js
-        }
-        localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry);
-        return localMap;
+    packPaths.reduce((map, entry) => {
+      const localMap = map;
+      const namespace = relative(join(entryPath), dirname(entry));
+      localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry);
+      return localMap;
+    }, {}),
+    localePackPaths.reduce((map, entry) => {
+      const localMap = map;
+      localMap[basename(entry, extname(entry, extname(entry)))] = resolve(entry);
+      return localMap;
+    }, {}),
+    Object.keys(themes).reduce(
+      (themePaths, name) => {
+        const themeData = themes[name];
+        themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack);
+        return themePaths;
       }, {}
-    ), themePaths
+    )
   ),
 
   output: {
@@ -58,7 +56,7 @@ module.exports = {
       }
     ),
     new ExtractTextPlugin({
-      filename: env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css',
+      filename: env.NODE_ENV === 'production' ? '[name]-[contenthash].css' : '[name].css',
       allChunks: true,
     }),
     new ManifestPlugin({
diff --git a/db/migrate/20171107143332_add_memorial_to_accounts.rb b/db/migrate/20171107143332_add_memorial_to_accounts.rb
new file mode 100644
index 000000000..f3e012ce8
--- /dev/null
+++ b/db/migrate/20171107143332_add_memorial_to_accounts.rb
@@ -0,0 +1,15 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddMemorialToAccounts < ActiveRecord::Migration[5.1]
+  include Mastodon::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    safety_assured { add_column_with_default :accounts, :memorial, :bool, default: false }
+  end
+
+  def down
+    remove_column :accounts, :memorial
+  end
+end
diff --git a/db/migrate/20171107143624_add_disabled_to_users.rb b/db/migrate/20171107143624_add_disabled_to_users.rb
new file mode 100644
index 000000000..a71cac1c6
--- /dev/null
+++ b/db/migrate/20171107143624_add_disabled_to_users.rb
@@ -0,0 +1,15 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddDisabledToUsers < ActiveRecord::Migration[5.1]
+  include Mastodon::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    safety_assured { add_column_with_default :users, :disabled, :bool, default: false }
+  end
+
+  def down
+    remove_column :users, :disabled
+  end
+end
diff --git a/db/migrate/20171109012327_add_moderator_to_accounts.rb b/db/migrate/20171109012327_add_moderator_to_accounts.rb
new file mode 100644
index 000000000..ddd87583a
--- /dev/null
+++ b/db/migrate/20171109012327_add_moderator_to_accounts.rb
@@ -0,0 +1,15 @@
+require Rails.root.join('lib', 'mastodon', 'migration_helpers')
+
+class AddModeratorToAccounts < ActiveRecord::Migration[5.1]
+  include Mastodon::MigrationHelpers
+
+  disable_ddl_transaction!
+
+  def up
+    safety_assured { add_column_with_default :users, :moderator, :bool, default: false }
+  end
+
+  def down
+    remove_column :users, :moderator
+  end
+end
diff --git a/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb
new file mode 100644
index 000000000..84a341510
--- /dev/null
+++ b/db/migrate/20171114080328_add_index_domain_to_email_domain_blocks.rb
@@ -0,0 +1,8 @@
+class AddIndexDomainToEmailDomainBlocks < ActiveRecord::Migration[5.1]
+  disable_ddl_transaction!
+
+  def change
+    add_index :email_domain_blocks, :domain, algorithm: :concurrently, unique: true
+    change_column_default :email_domain_blocks, :domain, from: nil, to: ''
+  end
+end
diff --git a/db/migrate/20171114231651_create_lists.rb b/db/migrate/20171114231651_create_lists.rb
new file mode 100644
index 000000000..21285e901
--- /dev/null
+++ b/db/migrate/20171114231651_create_lists.rb
@@ -0,0 +1,10 @@
+class CreateLists < ActiveRecord::Migration[5.1]
+  def change
+    create_table :lists do |t|
+      t.references :account, foreign_key: { on_delete: :cascade }
+      t.string :title, null: false, default: ''
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/migrate/20171116161857_create_list_accounts.rb b/db/migrate/20171116161857_create_list_accounts.rb
new file mode 100644
index 000000000..b76c90651
--- /dev/null
+++ b/db/migrate/20171116161857_create_list_accounts.rb
@@ -0,0 +1,12 @@
+class CreateListAccounts < ActiveRecord::Migration[5.1]
+  def change
+    create_table :list_accounts do |t|
+      t.belongs_to :list, foreign_key: { on_delete: :cascade }, null: false
+      t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false
+      t.belongs_to :follow, foreign_key: { on_delete: :cascade }, null: false
+    end
+
+    add_index :list_accounts, [:account_id, :list_id], unique: true
+    add_index :list_accounts, [:list_id, :account_id]
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 93505f9a0..10e35cd7d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20171028221157) do
+ActiveRecord::Schema.define(version: 20171116161857) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -71,6 +71,7 @@ ActiveRecord::Schema.define(version: 20171028221157) do
     t.string "shared_inbox_url", default: "", null: false
     t.string "followers_url", default: "", null: false
     t.integer "protocol", default: 0, null: false
+    t.boolean "memorial", default: false, null: false
     t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
     t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower"
     t.index ["uri"], name: "index_accounts_on_uri"
@@ -125,9 +126,10 @@ ActiveRecord::Schema.define(version: 20171028221157) do
   end
 
   create_table "email_domain_blocks", force: :cascade do |t|
-    t.string "domain", null: false
+    t.string "domain", default: "", null: false
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true
   end
 
   create_table "favourites", force: :cascade do |t|
@@ -179,6 +181,25 @@ ActiveRecord::Schema.define(version: 20171028221157) do
     t.bigint "account_id", null: false
   end
 
+  create_table "list_accounts", force: :cascade do |t|
+    t.bigint "list_id", null: false
+    t.bigint "account_id", null: false
+    t.bigint "follow_id", null: false
+    t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true
+    t.index ["account_id"], name: "index_list_accounts_on_account_id"
+    t.index ["follow_id"], name: "index_list_accounts_on_follow_id"
+    t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id"
+    t.index ["list_id"], name: "index_list_accounts_on_list_id"
+  end
+
+  create_table "lists", force: :cascade do |t|
+    t.bigint "account_id"
+    t.string "title", default: "", null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+    t.index ["account_id"], name: "index_lists_on_account_id"
+  end
+
   create_table "media_attachments", force: :cascade do |t|
     t.bigint "status_id"
     t.string "file_file_name"
@@ -447,6 +468,8 @@ ActiveRecord::Schema.define(version: 20171028221157) do
     t.string "otp_backup_codes", array: true
     t.string "filtered_languages", default: [], null: false, array: true
     t.bigint "account_id", null: false
+    t.boolean "disabled", default: false, null: false
+    t.boolean "moderator", default: false, null: false
     t.index ["account_id"], name: "index_users_on_account_id"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["email"], name: "index_users_on_email", unique: true
@@ -486,6 +509,10 @@ ActiveRecord::Schema.define(version: 20171028221157) do
   add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
   add_foreign_key "glitch_keyword_mutes", "accounts", on_delete: :cascade
   add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
+  add_foreign_key "list_accounts", "accounts", on_delete: :cascade
+  add_foreign_key "list_accounts", "follows", on_delete: :cascade
+  add_foreign_key "list_accounts", "lists", on_delete: :cascade
+  add_foreign_key "lists", "accounts", on_delete: :cascade
   add_foreign_key "media_attachments", "accounts", name: "fk_96dd81e81b", on_delete: :nullify
   add_foreign_key "media_attachments", "statuses", on_delete: :nullify
   add_foreign_key "mentions", "accounts", name: "fk_970d43f9d1", on_delete: :cascade
diff --git a/lib/mastodon/migration_helpers.rb b/lib/mastodon/migration_helpers.rb
index 80a8f440c..2b5a6cd42 100644
--- a/lib/mastodon/migration_helpers.rb
+++ b/lib/mastodon/migration_helpers.rb
@@ -84,7 +84,7 @@ module Mastodon
 
     BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job
     BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time
-    
+
     # Gets an estimated number of rows for a table
     def estimate_rows_in_table(table_name)
       exec_query('SELECT reltuples FROM pg_class WHERE relname = ' +
@@ -313,14 +313,14 @@ module Mastodon
       end
 
       table = Arel::Table.new(table_name)
-      
+
       total = estimate_rows_in_table(table_name).to_i
       if total == 0
         count_arel = table.project(Arel.star.count.as('count'))
         count_arel = yield table, count_arel if block_given?
-        
+
         total = exec_query(count_arel.to_sql).to_hash.first['count'].to_i
-        
+
         return if total == 0
       end
 
@@ -339,7 +339,7 @@ module Mastodon
       # In case there are no rows but we didn't catch it in the estimated size:
       return unless first_row
       start_id = first_row['id'].to_i
-      
+
       say "Migrating #{table_name}.#{column} (~#{total.to_i} rows)"
 
       started_time = Time.now
@@ -347,7 +347,7 @@ module Mastodon
       migrated = 0
       loop do
         stop_row = nil
-        
+
         suppress_messages do
           stop_arel = table.project(table[:id])
             .where(table[:id].gteq(start_id))
@@ -373,29 +373,29 @@ module Mastodon
 
           execute(update_arel.to_sql)
         end
-        
+
         migrated += batch_size
         if Time.now - last_time > 1
           status = "Migrated #{migrated} rows"
-          
+
           percentage = 100.0 * migrated / total
           status += " (~#{sprintf('%.2f', percentage)}%, "
-          
+
           remaining_time = (100.0 - percentage) * (Time.now - started_time) / percentage
-          
+
           status += "#{(remaining_time / 60).to_i}:"
           status += sprintf('%02d', remaining_time.to_i % 60)
           status += ' remaining, '
-          
+
           # Tell users not to interrupt if we're almost done.
           if remaining_time > 10
             status += 'safe to interrupt'
           else
             status += 'DO NOT interrupt'
           end
-          
+
           status += ')'
-          
+
           say status, true
           last_time = Time.now
         end
@@ -483,7 +483,7 @@ module Mastodon
 
       check_trigger_permissions!(table)
       trigger_name = rename_trigger_name(table, old, new)
-      
+
       # If we were in the middle of update_column_in_batches, we should remove
       # the old column and start over, as we have no idea where we were.
       if column_for(table, new)
@@ -492,7 +492,7 @@ module Mastodon
         else
           remove_rename_triggers_for_mysql(trigger_name)
         end
-        
+
         remove_column(table, new)
       end
 
@@ -546,12 +546,12 @@ module Mastodon
       temp_column = rename_column_name(column)
 
       rename_column_concurrently(table, column, temp_column, type: new_type)
-      
+
       # Primary keys don't necessarily have an associated index.
       if ActiveRecord::Base.get_primary_key(table) == column.to_s
         old_pk_index_name = "index_#{table}_on_#{column}"
         new_pk_index_name = "index_#{table}_on_#{column}_cm"
-        
+
         unless indexes_for(table, column).find{|i| i.name == old_pk_index_name}
           add_concurrent_index(table, [temp_column], {
             unique: true,
@@ -572,14 +572,14 @@ module Mastodon
       # Wait for the indices to be built
       indexes_for(table, column).each do |index|
         expected_name = index.name + '_cm'
-        
+
         puts "Waiting for index #{expected_name}"
         sleep 1 until indexes_for(table, temp_column).find {|i| i.name == expected_name }
       end
-      
+
       was_primary = (ActiveRecord::Base.get_primary_key(table) == column.to_s)
       old_default_fn = column_for(table, column).default_function
-      
+
       old_fks = []
       if was_primary
         # Get any foreign keys pointing at this column we need to recreate, and
@@ -613,7 +613,7 @@ module Mastodon
             target_col: temp_column,
             on_delete: extract_foreign_key_action(old_fk['on_delete'])
           )
-          
+
           remove_foreign_key(old_fk['src_table'], name: old_fk['name'])
         end
       end
@@ -629,15 +629,15 @@ module Mastodon
       transaction do
         # This has to be performed in a transaction as otherwise we might have
         # inconsistent data.
-        
+
         cleanup_concurrent_column_rename(table, column, temp_column)
         rename_column(table, temp_column, column)
-        
+
         # If there was an old default function, we didn't copy it. Do that now
         # in the transaction, so we don't miss anything.
         change_column_default(table, column, -> { old_default_fn }) if old_default_fn
       end
-      
+
       # Rename any indices back to what they should be.
       indexes_for(table, column).each do |index|
         next unless index.name.end_with?('_cm')
@@ -645,7 +645,7 @@ module Mastodon
         real_index_name = index.name.sub(/_cm$/, '')
         rename_index(table, index.name, real_index_name)
       end
-      
+
       # Rename any foreign keys back to names based on the real column.
       foreign_keys_for(table, column).each do |fk|
         old_fk_name = concurrent_foreign_key_name(fk.from_table, temp_column, 'id')
@@ -653,7 +653,7 @@ module Mastodon
         execute("ALTER TABLE #{fk.from_table} RENAME CONSTRAINT " +
           "#{old_fk_name} TO #{new_fk_name}")
       end
-      
+
       # Rename any foreign keys from other tables to names based on the real
       # column.
       old_fks.each do |old_fk|
@@ -664,7 +664,7 @@ module Mastodon
         execute("ALTER TABLE #{old_fk['src_table']} RENAME CONSTRAINT " +
           "#{old_fk_name} TO #{new_fk_name}")
       end
-      
+
       # If the old column was a primary key, mark the new one as a primary key.
       if was_primary
         execute("ALTER TABLE #{table} ADD PRIMARY KEY USING INDEX " +
@@ -791,7 +791,7 @@ module Mastodon
         # This is necessary as we can't properly rename indexes such as
         # "ci_taggings_idx".
         name = index.name + '_cm'
-        
+
         # If the order contained the old column, map it to the new one.
         order = index.orders
         if order.key?(old)
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 5614ddf48..995cf0d6f 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -10,14 +10,41 @@ namespace :mastodon do
   desc 'Turn a user into an admin, identified by the USERNAME environment variable'
   task make_admin: :environment do
     include RoutingHelper
+
     account_username = ENV.fetch('USERNAME')
-    user = User.joins(:account).where(accounts: { username: account_username })
+    user             = User.joins(:account).where(accounts: { username: account_username })
 
     if user.present?
       user.update(admin: true)
       puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started"
     else
-      puts "User could not be found; please make sure an Account with the `#{account_username}` username exists."
+      puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
+    end
+  end
+
+  desc 'Turn a user into a moderator, identified by the USERNAME environment variable'
+  task make_mod: :environment do
+    account_username = ENV.fetch('USERNAME')
+    user             = User.joins(:account).where(accounts: { username: account_username })
+
+    if user.present?
+      user.update(moderator: true)
+      puts "Congrats! #{account_username} is now a moderator \\o/"
+    else
+      puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
+    end
+  end
+
+  desc 'Remove admin and moderator privileges from user identified by the USERNAME environment variable'
+  task revoke_staff: :environment do
+    account_username = ENV.fetch('USERNAME')
+    user             = User.joins(:account).where(accounts: { username: account_username })
+
+    if user.present?
+      user.update(moderator: false, admin: false)
+      puts "#{account_username} is no longer admin or moderator."
+    else
+      puts "User could not be found; please make sure an account with the `#{account_username}` username exists."
     end
   end
 
@@ -81,7 +108,7 @@ namespace :mastodon do
     task remove_remote: :environment do
       time_ago = ENV.fetch('NUM_DAYS') { 7 }.to_i.days.ago
 
-      MediaAttachment.where.not(remote_url: '').where('created_at < ?', time_ago).find_each do |media|
+      MediaAttachment.where.not(remote_url: '').where.not(file_file_name: nil).where('created_at < ?', time_ago).find_each do |media|
         media.file.destroy
         media.save
       end
diff --git a/nanobox/nginx-web.conf.erb b/nanobox/nginx-web.conf.erb
index 24cd17cff..a839f3036 100644
--- a/nanobox/nginx-web.conf.erb
+++ b/nanobox/nginx-web.conf.erb
@@ -42,7 +42,12 @@ http {
             try_files $uri @rails;
         }
 
-        location ~ ^/(assets|system/media_attachments/files|system/accounts/avatars) {
+        location /sw.js {
+            add_header Cache-Control "public, max-age=0";
+            try_files $uri @rails;
+        }
+
+        location ~ ^/(emoji|packs|system/media_attachments/files|system/accounts/avatars) {
             add_header Cache-Control "public, max-age=31536000, immutable";
             try_files $uri @rails;
         }
diff --git a/package.json b/package.json
index 8d24c7850..159181030 100644
--- a/package.json
+++ b/package.json
@@ -20,14 +20,13 @@
   "dependencies": {
     "array-includes": "^3.0.3",
     "atrament": "^0.2.3",
-    "autoprefixer": "^7.1.2",
-    "axios": "^0.16.2",
+    "autoprefixer": "^7.1.6",
+    "axios": "~0.16.2",
     "babel-core": "^6.25.0",
     "babel-loader": "^7.1.1",
     "babel-plugin-lodash": "^3.2.11",
-    "babel-plugin-preval": "^1.3.2",
+    "babel-plugin-preval": "^1.6.1",
     "babel-plugin-react-intl": "^2.3.1",
-    "babel-plugin-react-transform": "^2.0.2",
     "babel-plugin-syntax-dynamic-import": "^6.18.0",
     "babel-plugin-transform-class-properties": "^6.24.1",
     "babel-plugin-transform-decorators-legacy": "^1.3.4",
@@ -36,30 +35,30 @@
     "babel-plugin-transform-react-inline-elements": "^6.22.0",
     "babel-plugin-transform-react-jsx-self": "^6.22.0",
     "babel-plugin-transform-react-jsx-source": "^6.22.0",
-    "babel-plugin-transform-react-remove-prop-types": "^0.4.6",
+    "babel-plugin-transform-react-remove-prop-types": "^0.4.10",
     "babel-plugin-transform-runtime": "^6.23.0",
-    "babel-preset-env": "^1.6.0",
+    "babel-preset-env": "^1.6.1",
     "babel-preset-react": "^6.24.1",
     "classnames": "^2.2.5",
-    "compression-webpack-plugin": "^0.4.0",
-    "cross-env": "^5.0.1",
+    "compression-webpack-plugin": "^1.0.1",
+    "cross-env": "^5.1.1",
     "css-loader": "^0.28.4",
     "detect-passive-events": "^1.0.2",
     "dotenv": "^4.0.0",
     "emoji-mart": "Gargron/emoji-mart#build",
     "es6-symbol": "^3.1.1",
     "escape-html": "^1.0.3",
-    "express": "^4.15.2",
-    "extract-text-webpack-plugin": "^2.1.2",
+    "express": "^4.16.2",
+    "extract-text-webpack-plugin": "^3.0.2",
     "file-loader": "^0.11.2",
     "font-awesome": "^4.7.0",
     "glob": "^7.1.1",
     "http-link-header": "^0.8.0",
-    "immutable": "^3.8.1",
+    "immutable": "^3.8.2",
     "intersection-observer": "^0.4.0",
     "intl": "^1.2.5",
     "intl-messageformat": "^2.1.0",
-    "intl-relativeformat": "^2.0.0",
+    "intl-relativeformat": "^2.1.0",
     "is-nan": "^1.2.1",
     "js-yaml": "^3.9.0",
     "lodash": "^4.17.4",
@@ -73,7 +72,7 @@
     "offline-plugin": "^4.8.3",
     "path-complete-extname": "^0.1.0",
     "pg": "^6.4.0",
-    "postcss-loader": "^2.0.6",
+    "postcss-loader": "^2.0.8",
     "postcss-object-fit-images": "^1.1.2",
     "postcss-smart-import": "^0.7.5",
     "precss": "^2.0.0",
@@ -84,15 +83,15 @@
     "react-dom": "^16.0.0",
     "react-hotkeys": "^0.10.0",
     "react-immutable-proptypes": "^2.1.0",
-    "react-immutable-pure-component": "^1.0.0",
+    "react-immutable-pure-component": "^1.1.1",
     "react-intl": "^2.4.0",
-    "react-motion": "^0.5.0",
-    "react-notification": "^6.7.1",
-    "react-overlays": "^0.8.1",
+    "react-motion": "^0.5.2",
+    "react-notification": "^6.8.2",
+    "react-overlays": "^0.8.3",
     "react-redux": "^5.0.4",
-    "react-redux-loading-bar": "^2.9.2",
+    "react-redux-loading-bar": "^2.9.3",
     "react-router-dom": "^4.1.1",
-    "react-router-scroll": "Gargron/react-router-scroll#build",
+    "react-router-scroll-4": "^1.0.0-beta.1",
     "react-swipeable-views": "^0.12.3",
     "react-textarea-autosize": "^5.0.7",
     "react-toggle": "^4.0.1",
@@ -102,17 +101,17 @@
     "redux-thunk": "^2.2.0",
     "requestidlecallback": "^0.3.0",
     "reselect": "^3.0.1",
-    "resolve-url-loader": "^2.1.0",
+    "resolve-url-loader": "^2.2.0",
     "rimraf": "^2.6.1",
     "sass-loader": "^6.0.6",
     "stringz": "^0.2.2",
-    "style-loader": "^0.18.2",
+    "style-loader": "^0.19.0",
     "substring-trie": "^1.0.2",
     "throng": "^4.0.0",
     "tiny-queue": "^0.2.1",
     "uuid": "^3.1.0",
     "uws": "^8.14.0",
-    "webpack": "^3.4.1",
+    "webpack": "^3.8.1",
     "webpack-bundle-analyzer": "^2.8.3",
     "webpack-manifest-plugin": "^1.2.1",
     "webpack-merge": "^4.1.0",
@@ -121,19 +120,20 @@
   "devDependencies": {
     "babel-eslint": "^7.2.3",
     "enzyme": "^3.0.0",
-    "enzyme-adapter-react-16": "^1.0.0",
+    "enzyme-adapter-react-16": "^1.0.2",
     "eslint": "^3.19.0",
-    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-import": "^2.8.0",
     "eslint-plugin-jsx-a11y": "^4.0.0",
     "eslint-plugin-react": "^6.10.3",
     "jest": "^21.2.1",
     "raf": "^3.4.0",
     "react-intl-translations-manager": "^5.0.0",
     "react-test-renderer": "^16.0.0",
-    "webpack-dev-server": "^2.6.1",
+    "webpack-dev-server": "^2.9.3",
     "yargs": "^8.0.2"
   },
   "optionalDependencies": {
-    "fsevents": "*"
+    "fsevents": "*",
+    "node-zopfli": "^2.0.2"
   }
 }
diff --git a/public/sounds/boop.mp3 b/public/sounds/boop.mp3
index 02a035d91..bf9c3c1aa 100644
--- a/public/sounds/boop.mp3
+++ b/public/sounds/boop.mp3
Binary files differdiff --git a/public/sounds/boop.ogg b/public/sounds/boop.ogg
index a2124e116..a6551c9fd 100644
--- a/public/sounds/boop.ogg
+++ b/public/sounds/boop.ogg
Binary files differdiff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb
index 92f888590..a8ade790c 100644
--- a/spec/controllers/accounts_controller_spec.rb
+++ b/spec/controllers/accounts_controller_spec.rb
@@ -4,6 +4,7 @@ RSpec.describe AccountsController, type: :controller do
   render_views
 
   let(:alice)  { Fabricate(:account, username: 'alice') }
+  let(:eve)  { Fabricate(:user) }
 
   describe 'GET #show' do
     let!(:status1) { Status.create!(account: alice, text: 'Hello world') }
@@ -19,93 +20,123 @@ RSpec.describe AccountsController, type: :controller do
     let!(:status_pin3) { StatusPin.create!(account: alice, status: status7, created_at: 10.minutes.ago) }
 
     before do
+      alice.block!(eve.account)
       status3.media_attachments.create!(account: alice, file: fixture_file_upload('files/attachment.jpg', 'image/jpeg'))
     end
 
-    context 'atom' do
+    shared_examples 'responses' do
       before do
-        get :show, params: { username: alice.username, max_id: status4.stream_entry.id, since_id: status1.stream_entry.id }, format: 'atom'
+        sign_in(current_user) if defined? current_user
+        get :show, params: {
+          username: alice.username,
+          max_id: (max_id if defined? max_id),
+          since_id: (since_id if defined? since_id),
+          current_user: (current_user if defined? current_user),
+        }, format: format
       end
 
       it 'assigns @account' do
         expect(assigns(:account)).to eq alice
       end
 
-      it 'assigns @entries' do
-        entries = assigns(:entries).to_a
-        expect(entries.size).to eq 2
-        expect(entries[0].status).to eq status3
-        expect(entries[1].status).to eq status2
+      it 'returns http success' do
+        expect(response).to have_http_status(:success)
       end
 
-      it 'returns http success with Atom' do
-        expect(response).to have_http_status(:success)
+      it 'returns correct format' do
+        expect(response.content_type).to eq content_type
       end
     end
 
-    context 'activitystreams2' do
-      before do
-        get :show, params: { username: alice.username }, format: 'json'
-      end
+    context 'atom' do
+      let(:format) { 'atom' }
+      let(:content_type) { 'application/atom+xml' }
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
+      shared_examples 'responsed streams' do
+        it 'assigns @entries' do
+          entries = assigns(:entries).to_a
+          expect(entries.size).to eq expected_statuses.size
+          entries.each.zip(expected_statuses.each) do |entry, expected_status|
+            expect(entry.status).to eq expected_status
+          end
+        end
       end
 
-      it 'returns http success with Activity Streams 2.0' do
-        expect(response).to have_http_status(:success)
-      end
+      include_examples 'responses'
 
-      it 'returns application/activity+json' do
-        expect(response.content_type).to eq 'application/activity+json'
-      end
-    end
+      context 'without max_id nor since_id' do
+        let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
 
-    context 'html without since_id nor max_id' do
-      before do
-        get :show, params: { username: alice.username }
+        include_examples 'responsed streams'
       end
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
-      end
+      context 'with max_id and since_id' do
+        let(:max_id) { status4.stream_entry.id }
+        let(:since_id) { status1.stream_entry.id }
+        let(:expected_statuses) { [status3, status2] }
 
-      it 'assigns @pinned_statuses' do
-        pinned_statuses = assigns(:pinned_statuses).to_a
-        expect(pinned_statuses.size).to eq 3
-        expect(pinned_statuses[0]).to eq status7
-        expect(pinned_statuses[1]).to eq status5
-        expect(pinned_statuses[2]).to eq status6
+        include_examples 'responsed streams'
       end
+    end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(:success)
-      end
+    context 'activitystreams2' do
+      let(:format) { 'json' }
+      let(:content_type) { 'application/activity+json' }
+
+      include_examples 'responses'
     end
 
-    context 'html with since_id and max_id' do
-      before do
-        get :show, params: { username: alice.username, max_id: status4.id, since_id: status1.id }
-      end
+    context 'html' do
+      let(:format) { nil }
+      let(:content_type) { 'text/html' }
 
-      it 'assigns @account' do
-        expect(assigns(:account)).to eq alice
-      end
+      shared_examples 'responsed statuses' do
+        it 'assigns @pinned_statuses' do
+          pinned_statuses = assigns(:pinned_statuses).to_a
+          expect(pinned_statuses.size).to eq expected_pinned_statuses.size
+          pinned_statuses.each.zip(expected_pinned_statuses.each) do |pinned_status, expected_pinned_status|
+            expect(pinned_status).to eq expected_pinned_status
+          end
+        end
 
-      it 'assigns @statuses' do
-        statuses = assigns(:statuses).to_a
-        expect(statuses.size).to eq 2
-        expect(statuses[0]).to eq status3
-        expect(statuses[1]).to eq status2
+        it 'assigns @statuses' do
+          statuses = assigns(:statuses).to_a
+          expect(statuses.size).to eq expected_statuses.size
+          statuses.each.zip(expected_statuses.each) do |status, expected_status|
+            expect(status).to eq expected_status
+          end
+        end
       end
 
-      it 'assigns an empty array to @pinned_statuses' do
-        pinned_statuses = assigns(:pinned_statuses).to_a
-        expect(pinned_statuses.size).to eq 0
+      include_examples 'responses'
+
+      context 'with anonymous visitor' do
+        context 'without since_id nor max_id' do
+          let(:expected_statuses) { [status7, status6, status5, status4, status3, status2, status1] }
+          let(:expected_pinned_statuses) { [status7, status5, status6] }
+
+          include_examples 'responsed statuses'
+        end
+
+        context 'with since_id nor max_id' do
+          let(:max_id) { status4.id }
+          let(:since_id) { status1.id }
+          let(:expected_statuses) { [status3, status2] }
+          let(:expected_pinned_statuses) { [] }
+
+          include_examples 'responsed statuses'
+        end
       end
 
-      it 'returns http success' do
-        expect(response).to have_http_status(:success)
+      context 'with blocked visitor' do
+        let(:current_user) { eve }
+
+        context 'without since_id nor max_id' do
+          let(:expected_statuses) { [] }
+          let(:expected_pinned_statuses) { [] }
+
+          include_examples 'responsed statuses'
+        end
       end
     end
   end
diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb
new file mode 100644
index 000000000..953e5909d
--- /dev/null
+++ b/spec/controllers/api/v1/lists/accounts_controller_spec.rb
@@ -0,0 +1,54 @@
+require 'rails_helper'
+
+describe Api::V1::Lists::AccountsController do
+  render_views
+
+  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read write') }
+  let(:list)  { Fabricate(:list, account: user.account) }
+
+  before do
+    follow = Fabricate(:follow, account: user.account)
+    list.accounts << follow.target_account
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :show, params: { list_id: list.id }
+
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'POST #create' do
+    let(:bob) { Fabricate(:account, username: 'bob') }
+
+    before do
+      user.account.follow!(bob)
+      post :create, params: { list_id: list.id, account_ids: [bob.id] }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'adds account to the list' do
+      expect(list.accounts.include?(bob)).to be true
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    before do
+      delete :destroy, params: { list_id: list.id, account_ids: [list.accounts.first.id] }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'removes account from the list' do
+      expect(list.accounts.count).to eq 0
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb
new file mode 100644
index 000000000..be08c221f
--- /dev/null
+++ b/spec/controllers/api/v1/lists_controller_spec.rb
@@ -0,0 +1,68 @@
+require 'rails_helper'
+
+RSpec.describe Api::V1::ListsController, type: :controller do
+  render_views
+
+  let!(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read write') }
+  let!(:list)  { Fabricate(:list, account: user.account) }
+
+  before { allow(controller).to receive(:doorkeeper_token) { token } }
+
+  describe 'GET #index' do
+    it 'returns http success' do
+      get :index
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'GET #show' do
+    it 'returns http success' do
+      get :show, params: { id: list.id }
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+  describe 'POST #create' do
+    before do
+      post :create, params: { title: 'Foo bar' }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'creates list' do
+      expect(List.where(account: user.account).count).to eq 2
+      expect(List.last.title).to eq 'Foo bar'
+    end
+  end
+
+  describe 'PUT #update' do
+    before do
+      put :update, params: { id: list.id, title: 'Updated title' }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'updates the list' do
+      expect(list.reload.title).to eq 'Updated title'
+    end
+  end
+
+  describe 'DELETE #destroy' do
+    before do
+      delete :destroy, params: { id: list.id }
+    end
+
+    it 'returns http success' do
+      expect(response).to have_http_status(:success)
+    end
+
+    it 'deletes the list' do
+      expect(List.find_by(id: list.id)).to be_nil
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/timelines/list_controller_spec.rb b/spec/controllers/api/v1/timelines/list_controller_spec.rb
new file mode 100644
index 000000000..07eba955a
--- /dev/null
+++ b/spec/controllers/api/v1/timelines/list_controller_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Api::V1::Timelines::ListController do
+  render_views
+
+  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:list) { Fabricate(:list, account: user.account) }
+
+  before do
+    allow(controller).to receive(:doorkeeper_token) { token }
+  end
+
+  context 'with a user context' do
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') }
+
+    describe 'GET #show' do
+      before do
+        follow = Fabricate(:follow, account: user.account)
+        list.accounts << follow.target_account
+        PostStatusService.new.call(follow.target_account, 'New status for user home timeline.')
+      end
+
+      it 'returns http success' do
+        get :show, params: { id: list.id }
+        expect(response).to have_http_status(:success)
+      end
+    end
+  end
+
+  context 'with the wrong user context' do
+    let(:other_user) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
+    let(:token)      { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') }
+
+    describe 'GET #show' do
+      it 'returns http not found' do
+        get :show, params: { id: list.id }
+        expect(response).to have_http_status(:not_found)
+      end
+    end
+  end
+
+  context 'without a user context' do
+    let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') }
+
+    describe 'GET #show' do
+      it 'returns http unprocessable entity' do
+        get :show, params: { id: list.id }
+
+        expect(response).to have_http_status(:unprocessable_entity)
+        expect(response.headers['Link']).to be_nil
+      end
+    end
+  end
+end
diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
index 74de1e81f..6c66ee58e 100644
--- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb
+++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 describe Api::V1::Timelines::TagController do
   render_views
 
-  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+  let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
 
   before do
     allow(controller).to receive(:doorkeeper_token) { token }
diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb
index ca66f8d23..90e6a63d5 100644
--- a/spec/controllers/settings/applications_controller_spec.rb
+++ b/spec/controllers/settings/applications_controller_spec.rb
@@ -2,10 +2,10 @@ require 'rails_helper'
 
 describe Settings::ApplicationsController do
   render_views
-  
+
   let!(:user) { Fabricate(:user) }
   let!(:app) { Fabricate(:application, owner: user) }
-  
+
   before do
     sign_in user, scope: :user
   end
@@ -21,7 +21,7 @@ describe Settings::ApplicationsController do
     end
   end
 
-  
+
   describe 'GET #show' do
     it 'returns http success' do
       get :show, params: { id: app.id }
@@ -110,7 +110,7 @@ describe Settings::ApplicationsController do
       end
     end
   end
-  
+
   describe 'PATCH #update' do
     context 'success' do
       let(:opts) {
@@ -131,7 +131,7 @@ describe Settings::ApplicationsController do
         call_update
         expect(app.reload.website).to eql(opts[:website])
       end
-      
+
       it 'redirects back to applications page' do
         expect(call_update).to redirect_to(settings_applications_path)
       end
diff --git a/spec/fabricators/list_account_fabricator.rb b/spec/fabricators/list_account_fabricator.rb
new file mode 100644
index 000000000..30e4004aa
--- /dev/null
+++ b/spec/fabricators/list_account_fabricator.rb
@@ -0,0 +1,5 @@
+Fabricator(:list_account) do
+  list    nil
+  account nil
+  follow  nil
+end
diff --git a/spec/fabricators/list_fabricator.rb b/spec/fabricators/list_fabricator.rb
new file mode 100644
index 000000000..d249c2029
--- /dev/null
+++ b/spec/fabricators/list_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:list) do
+  account nil
+  title   "MyString"
+end
diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb
index 46050bdab..526faaec2 100644
--- a/spec/fabricators/session_activation_fabricator.rb
+++ b/spec/fabricators/session_activation_fabricator.rb
@@ -1,4 +1,4 @@
 Fabricator(:session_activation) do
-  user_id    1
+  user
   session_id "MyString"
 end
diff --git a/spec/fabricators/setting_fabricator.rb b/spec/fabricators/setting_fabricator.rb
new file mode 100644
index 000000000..336d7c355
--- /dev/null
+++ b/spec/fabricators/setting_fabricator.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+Fabricator(:setting) do
+end
diff --git a/spec/fixtures/requests/attachment1.txt b/spec/fixtures/requests/attachment1.txt
index 77fd9c836..30bd456be 100644
--- a/spec/fixtures/requests/attachment1.txt
+++ b/spec/fixtures/requests/attachment1.txt
Binary files differdiff --git a/spec/fixtures/requests/attachment2.txt b/spec/fixtures/requests/attachment2.txt
index 917a1d398..2a252d2de 100644
--- a/spec/fixtures/requests/attachment2.txt
+++ b/spec/fixtures/requests/attachment2.txt
Binary files differdiff --git a/spec/fixtures/requests/avatar.txt b/spec/fixtures/requests/avatar.txt
index d57b0984f..d771f5dda 100644
--- a/spec/fixtures/requests/avatar.txt
+++ b/spec/fixtures/requests/avatar.txt
Binary files differdiff --git a/spec/fixtures/requests/idn.txt b/spec/fixtures/requests/idn.txt
index 3c76c59c0..5d07f2b79 100644
--- a/spec/fixtures/requests/idn.txt
+++ b/spec/fixtures/requests/idn.txt
@@ -6,7 +6,7 @@ Content-Length: 38111
 Last-Modified: Wed, 20 Jul 2016 02:50:52 GMT

 Connection: keep-alive

 Accept-Ranges: bytes

-

+
 <!DOCTYPE html>

 <html>

 	<head>

@@ -21,16 +21,16 @@ Accept-Ranges: bytes
             var s = document.getElementsByTagName("script")[0]; 

             s.parentNode.insertBefore(hm, s);

           })();

-	

+
     	</script>

-

-

+
+
 		<link rel="stylesheet" type="text/css" href="css/common.css"/>

 		<script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>

 		<script src="js/common.js" type="text/javascript" charset="utf-8"></script>

 		<script src="js/carousel.js" type="text/javascript" charset="utf-8"></script>

 		<title>中国域名网站</title>

-

+
 	</head>

 	<body>

 		<div class="head-tips" id="headTip">

@@ -453,7 +453,7 @@ Accept-Ranges: bytes
 					<li><a href="http://新疆农业大学.中国" target="_blank">新疆农业大学.中国</a></li>

 					<li><a href="http://浙江万里学院.中国" target="_blank">浙江万里学院.中国</a></li>

 					<li><a href="http://重庆大学.中国" target="_blank">重庆大学.中国</a></li>

-					

+
 				</ul>

 			</div>

 		</div>

@@ -472,7 +472,7 @@ Accept-Ranges: bytes
 	<script>

 	$("#headTip").hide()

 	var hostname = window.location.hostname || "";

-

+
 	var tips =  "您所访问的域名 <font size='' color='#ff0000'>" + hostname +"</font> 无法到达,您可以尝试重新访问,或使用搜索相关信息"

 	if (hostname != "导航.中国") {

 		$("#headTip").html(tips);

diff --git a/spec/helpers/stream_entries_helper_spec.rb b/spec/helpers/stream_entries_helper_spec.rb
index 2c0d7b239..1de6691ba 100644
--- a/spec/helpers/stream_entries_helper_spec.rb
+++ b/spec/helpers/stream_entries_helper_spec.rb
@@ -77,7 +77,7 @@ RSpec.describe StreamEntriesHelper, type: :helper do
     params[:controller] = StreamEntriesHelper::EMBEDDED_CONTROLLER
     params[:action] = StreamEntriesHelper::EMBEDDED_ACTION
   end
-  
+
   describe '#style_classes' do
     it do
       status = double(reblog?: false)
@@ -202,7 +202,7 @@ RSpec.describe StreamEntriesHelper, type: :helper do
       expect(css_class).to eq 'h-cite'
     end
   end
-  
+
   describe '#rtl?' do
     it 'is false if text is empty' do
       expect(helper).not_to be_rtl ''
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 0e4968440..f87ef383a 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -223,21 +223,11 @@ RSpec.describe FeedManager do
       account = Fabricate(:account)
       status = Fabricate(:status)
       members = FeedManager::MAX_ITEMS.times.map { |count| [count, count] }
-      Redis.current.zadd("feed:type:#{account.id}", members)
+      Redis.current.zadd("feed:home:#{account.id}", members)
 
-      FeedManager.instance.push('type', account, status)
+      FeedManager.instance.push_to_home(account, status)
 
-      expect(Redis.current.zcard("feed:type:#{account.id}")).to eq FeedManager::MAX_ITEMS
-    end
-
-    it 'sends push updates for non-home timelines' do
-      account = Fabricate(:account)
-      status = Fabricate(:status)
-      allow(Redis.current).to receive_messages(publish: nil)
-
-      FeedManager.instance.push('type', account, status)
-
-      expect(Redis.current).to have_received(:publish).with("timeline:#{account.id}", any_args).at_least(:once)
+      expect(Redis.current.zcard("feed:home:#{account.id}")).to eq FeedManager::MAX_ITEMS
     end
 
     context 'reblogs' do
@@ -246,7 +236,7 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        expect(FeedManager.instance.push('type', account, reblog)).to be true
+        expect(FeedManager.instance.push_to_home(account, reblog)).to be true
       end
 
       it 'does not save a new reblog of a recent status' do
@@ -254,9 +244,9 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        FeedManager.instance.push('type', account, reblogged)
+        FeedManager.instance.push_to_home(account, reblogged)
 
-        expect(FeedManager.instance.push('type', account, reblog)).to be false
+        expect(FeedManager.instance.push_to_home(account, reblog)).to be false
       end
 
       it 'saves a new reblog of an old status' do
@@ -264,14 +254,14 @@ RSpec.describe FeedManager do
         reblogged = Fabricate(:status)
         reblog = Fabricate(:status, reblog: reblogged)
 
-        FeedManager.instance.push('type', account, reblogged)
+        FeedManager.instance.push_to_home(account, reblogged)
 
         # Fill the feed with intervening statuses
         FeedManager::REBLOG_FALLOFF.times do
-          FeedManager.instance.push('type', account, Fabricate(:status))
+          FeedManager.instance.push_to_home(account, Fabricate(:status))
         end
 
-        expect(FeedManager.instance.push('type', account, reblog)).to be true
+        expect(FeedManager.instance.push_to_home(account, reblog)).to be true
       end
 
       it 'does not save a new reblog of a recently-reblogged status' do
@@ -280,10 +270,10 @@ RSpec.describe FeedManager do
         reblogs = 2.times.map { Fabricate(:status, reblog: reblogged) }
 
         # The first reblog will be accepted
-        FeedManager.instance.push('type', account, reblogs.first)
+        FeedManager.instance.push_to_home(account, reblogs.first)
 
         # The second reblog should be ignored
-        expect(FeedManager.instance.push('type', account, reblogs.last)).to be false
+        expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be false
       end
 
       it 'does not save a new reblog of a multiply-reblogged-then-unreblogged status' do
@@ -292,14 +282,14 @@ RSpec.describe FeedManager do
         reblogs = 3.times.map { Fabricate(:status, reblog: reblogged) }
 
         # Accept the reblogs
-        FeedManager.instance.push('type', account, reblogs[0])
-        FeedManager.instance.push('type', account, reblogs[1])
+        FeedManager.instance.push_to_home(account, reblogs[0])
+        FeedManager.instance.push_to_home(account, reblogs[1])
 
         # Unreblog the first one
-        FeedManager.instance.unpush('type', account, reblogs[0])
+        FeedManager.instance.unpush_from_home(account, reblogs[0])
 
         # The last reblog should still be ignored
-        expect(FeedManager.instance.push('type', account, reblogs.last)).to be false
+        expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be false
       end
 
       it 'saves a new reblog of a long-ago-reblogged status' do
@@ -308,15 +298,15 @@ RSpec.describe FeedManager do
         reblogs = 2.times.map { Fabricate(:status, reblog: reblogged) }
 
         # The first reblog will be accepted
-        FeedManager.instance.push('type', account, reblogs.first)
+        FeedManager.instance.push_to_home(account, reblogs.first)
 
         # Fill the feed with intervening statuses
         FeedManager::REBLOG_FALLOFF.times do
-          FeedManager.instance.push('type', account, Fabricate(:status))
+          FeedManager.instance.push_to_home(account, Fabricate(:status))
         end
 
         # The second reblog should also be accepted
-        expect(FeedManager.instance.push('type', account, reblogs.last)).to be true
+        expect(FeedManager.instance.push_to_home(account, reblogs.last)).to be true
       end
     end
   end
@@ -328,11 +318,11 @@ RSpec.describe FeedManager do
       reblogged      = Fabricate(:status)
       status         = Fabricate(:status, reblog: reblogged)
       another_status = Fabricate(:status, reblog: reblogged)
-      reblogs_key    = FeedManager.instance.key('type', receiver.id, 'reblogs')
-      reblog_set_key = FeedManager.instance.key('type', receiver.id, "reblogs:#{reblogged.id}")
+      reblogs_key    = FeedManager.instance.key('home', receiver.id, 'reblogs')
+      reblog_set_key = FeedManager.instance.key('home', receiver.id, "reblogs:#{reblogged.id}")
 
-      FeedManager.instance.push('type', receiver, status)
-      FeedManager.instance.push('type', receiver, another_status)
+      FeedManager.instance.push_to_home(receiver, status)
+      FeedManager.instance.push_to_home(receiver, another_status)
 
       # We should have a tracking set and an entry in reblogs.
       expect(Redis.current.exists(reblog_set_key)).to be true
@@ -340,12 +330,12 @@ RSpec.describe FeedManager do
 
       # Push everything off the end of the feed.
       FeedManager::MAX_ITEMS.times do
-        FeedManager.instance.push('type', receiver, Fabricate(:status))
+        FeedManager.instance.push_to_home(receiver, Fabricate(:status))
       end
 
       # `trim` should be called automatically, but do it anyway, as
       # we're testing `trim`, not side effects of `push`.
-      FeedManager.instance.trim('type', receiver.id)
+      FeedManager.instance.trim('home', receiver.id)
 
       # We should not have any reblog tracking data.
       expect(Redis.current.exists(reblog_set_key)).to be false
@@ -360,32 +350,32 @@ RSpec.describe FeedManager do
       reblogged = Fabricate(:status)
       status    = Fabricate(:status, reblog: reblogged)
 
-      FeedManager.instance.push('type', receiver, reblogged)
-      FeedManager::REBLOG_FALLOFF.times { FeedManager.instance.push('type', receiver, Fabricate(:status)) }
-      FeedManager.instance.push('type', receiver, status)
+      FeedManager.instance.push_to_home(receiver, reblogged)
+      FeedManager::REBLOG_FALLOFF.times { FeedManager.instance.push_to_home(receiver, Fabricate(:status)) }
+      FeedManager.instance.push_to_home(receiver, status)
 
       # The reblogging status should show up under normal conditions.
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to include(status.id.to_s)
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s)
 
-      FeedManager.instance.unpush('type', receiver, status)
+      FeedManager.instance.unpush_from_home(receiver, status)
 
       # Restore original status
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to include(reblogged.id.to_s)
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s)
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to include(reblogged.id.to_s)
     end
 
     it 'removes a reblogged status if it was only reblogged once' do
       reblogged = Fabricate(:status)
       status    = Fabricate(:status, reblog: reblogged)
 
-      FeedManager.instance.push('type', receiver, status)
+      FeedManager.instance.push_to_home(receiver, status)
 
       # The reblogging status should show up under normal conditions.
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [status.id.to_s]
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [status.id.to_s]
 
-      FeedManager.instance.unpush('type', receiver, status)
+      FeedManager.instance.unpush_from_home(receiver, status)
 
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to be_empty
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to be_empty
     end
 
     it 'leaves a multiply-reblogged status if another reblog was in feed' do
@@ -393,26 +383,26 @@ RSpec.describe FeedManager do
       reblogs   = 3.times.map { Fabricate(:status, reblog: reblogged) }
 
       reblogs.each do |reblog|
-        FeedManager.instance.push('type', receiver, reblog)
+        FeedManager.instance.push_to_home(receiver, reblog)
       end
 
       # The reblogging status should show up under normal conditions.
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [reblogs.first.id.to_s]
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.first.id.to_s]
 
       reblogs[0...-1].each do |reblog|
-        FeedManager.instance.unpush('type', receiver, reblog)
+        FeedManager.instance.unpush_from_home(receiver, reblog)
       end
 
-      expect(Redis.current.zrange("feed:type:#{receiver.id}", 0, -1)).to eq [reblogs.last.id.to_s]
+      expect(Redis.current.zrange("feed:home:#{receiver.id}", 0, -1)).to eq [reblogs.last.id.to_s]
     end
 
     it 'sends push updates' do
       status  = Fabricate(:status)
 
-      FeedManager.instance.push('type', receiver, status)
+      FeedManager.instance.push_to_home(receiver, status)
 
       allow(Redis.current).to receive_messages(publish: nil)
-      FeedManager.instance.unpush('type', receiver, status)
+      FeedManager.instance.unpush_from_home(receiver, status)
 
       deletion = Oj.dump(event: :delete, payload: status.id.to_s)
       expect(Redis.current).to have_received(:publish).with("timeline:#{receiver.id}", deletion)
diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb
new file mode 100644
index 000000000..7566685b4
--- /dev/null
+++ b/spec/lib/settings/scoped_settings_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Settings::ScopedSettings do
+  let(:object)         { Fabricate(:user) }
+  let(:scoped_setting) { described_class.new(object) }
+  let(:val)            { 'whatever' }
+  let(:methods)        { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) }
+
+  describe '.initialize' do
+    it 'sets @object' do
+      scoped_setting = described_class.new(object)
+      expect(scoped_setting.instance_variable_get(:@object)).to be object
+    end
+  end
+
+  describe '#method_missing' do
+    it 'sets scoped_setting.method_name = val' do
+      methods.each do |key|
+        scoped_setting.send("#{key}=", val)
+        expect(scoped_setting.send(key)).to eq val
+      end
+    end
+  end
+
+  describe '#[]= and #[]' do
+    it 'sets [key] = val' do
+      methods.each do |key|
+        scoped_setting[key] = val
+        expect(scoped_setting[key]).to eq val
+      end
+    end
+  end
+end
diff --git a/spec/lib/user_settings_decorator_spec.rb b/spec/lib/user_settings_decorator_spec.rb
index 6fbf6536b..fee875373 100644
--- a/spec/lib/user_settings_decorator_spec.rb
+++ b/spec/lib/user_settings_decorator_spec.rb
@@ -62,7 +62,7 @@ describe UserSettingsDecorator do
       settings.update(values)
       expect(user.settings['auto_play_gif']).to eq false
     end
-    
+
     it 'updates the user settings value for system font in UI' do
       values = { 'setting_system_font_ui' => '0' }
 
diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb
index c4be8c4af..16983b2e3 100644
--- a/spec/models/account_moderation_note_spec.rb
+++ b/spec/models/account_moderation_note_spec.rb
@@ -1,5 +1,5 @@
 require 'rails_helper'
 
 RSpec.describe AccountModerationNote, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+
 end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 361577eff..7501c498c 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -93,21 +93,44 @@ RSpec.describe Account, type: :model do
   end
 
   describe '#save_with_optional_media!' do
-    it 'sets default avatar, header, avatar_remote_url, and header_remote_url if some of them are invalid' do
+    before do
       stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt'))
       stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt'))
-      account = Fabricate(:account,
-                          avatar_remote_url: 'https://remote/valid_avatar',
-                          header_remote_url: 'https://remote/valid_avatar')
+    end
+
+    let(:account) do
+      Fabricate(:account,
+                avatar_remote_url: 'https://remote/valid_avatar',
+                header_remote_url: 'https://remote/valid_avatar')
+    end
+
+    let!(:expectation) { account.dup }
+
+    context 'with valid properties' do
+      before do
+        account.save_with_optional_media!
+      end
+
+      it 'unchanges avatar, header, avatar_remote_url, and header_remote_url' do
+        expect(account.avatar_remote_url).to eq expectation.avatar_remote_url
+        expect(account.header_remote_url).to eq expectation.header_remote_url
+        expect(account.avatar_file_name).to  eq expectation.avatar_file_name
+        expect(account.header_file_name).to  eq expectation.header_file_name
+      end
+    end
 
-      account.avatar_remote_url = 'https://remote/invalid_avatar'
-      account.save_with_optional_media!
+    context 'with invalid properties' do
+      before do
+        account.avatar_remote_url = 'https://remote/invalid_avatar'
+        account.save_with_optional_media!
+      end
 
-      account.reload
-      expect(account.avatar_remote_url).to eq ''
-      expect(account.header_remote_url).to eq ''
-      expect(account.avatar_file_name).to eq nil
-      expect(account.header_file_name).to eq nil
+      it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
+        expect(account.avatar_remote_url).to eq ''
+        expect(account.header_remote_url).to eq ''
+        expect(account.avatar_file_name).to  eq nil
+        expect(account.header_file_name).to  eq nil
+      end
     end
   end
 
@@ -123,6 +146,61 @@ RSpec.describe Account, type: :model do
     end
   end
 
+  describe '#possibly_stale?' do
+    let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) }
+
+    context 'last_webfingered_at is nil' do
+      let(:last_webfingered_at) { nil }
+
+      it 'returns true' do
+        expect(account.possibly_stale?).to be true
+      end
+    end
+
+    context 'last_webfingered_at is more than 24 hours before' do
+      let(:last_webfingered_at) { 25.hours.ago }
+
+      it 'returns true' do
+        expect(account.possibly_stale?).to be true
+      end
+    end
+
+    context 'last_webfingered_at is less than 24 hours before' do
+      let(:last_webfingered_at) { 23.hours.ago }
+
+      it 'returns false' do
+        expect(account.possibly_stale?).to be false
+      end
+    end
+  end
+
+  describe '#refresh!' do
+    let(:account) { Fabricate(:account, domain: domain) }
+    let(:acct)    { account.acct }
+
+    context 'domain is nil' do
+      let(:domain) { nil }
+
+      it 'returns nil' do
+        expect(account.refresh!).to be_nil
+      end
+
+      it 'calls not ResolveRemoteAccountService#call' do
+        expect_any_instance_of(ResolveRemoteAccountService).not_to receive(:call).with(acct)
+        account.refresh!
+      end
+    end
+
+    context 'domain is present' do
+      let(:domain) { 'example.com' }
+
+      it 'calls ResolveRemoteAccountService#call' do
+        expect_any_instance_of(ResolveRemoteAccountService).to receive(:call).with(acct).once
+        account.refresh!
+      end
+    end
+  end
+
   describe '#to_param' do
     it 'returns username' do
       account = Fabricate(:account, username: 'alice')
diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb
index f47d9d057..1e238e27c 100644
--- a/spec/models/concerns/account_interactions_spec.rb
+++ b/spec/models/concerns/account_interactions_spec.rb
@@ -2,38 +2,36 @@ require 'rails_helper'
 
 describe AccountInteractions do
   describe 'muting an account' do
-    before do
-      @me = Fabricate(:account, username: 'Me')
-      @you = Fabricate(:account, username: 'You')
-    end
+    let(:me) { Fabricate(:account, username: 'Me') }
+    let(:you) { Fabricate(:account, username: 'You') }
 
     context 'with the notifications option unspecified' do
       before do
-        @me.mute!(@you)
+        me.mute!(you)
       end
 
       it 'defaults to muting notifications' do
-        expect(@me.muting_notifications?(@you)).to be(true)
+        expect(me.muting_notifications?(you)).to be true
       end
     end
 
     context 'with the notifications option set to false' do
       before do
-        @me.mute!(@you, notifications: false)
+        me.mute!(you, notifications: false)
       end
 
       it 'does not mute notifications' do
-        expect(@me.muting_notifications?(@you)).to be(false)
+        expect(me.muting_notifications?(you)).to be false
       end
     end
 
     context 'with the notifications option set to true' do
       before do
-        @me.mute!(@you, notifications: true)
+        me.mute!(you, notifications: true)
       end
 
       it 'does mute notifications' do
-        expect(@me.muting_notifications?(@you)).to be(true)
+        expect(me.muting_notifications?(you)).to be true 
       end
     end
   end
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
index cb51e9519..bb150b837 100644
--- a/spec/models/custom_emoji_spec.rb
+++ b/spec/models/custom_emoji_spec.rb
@@ -1,6 +1,35 @@
 require 'rails_helper'
 
 RSpec.describe CustomEmoji, type: :model do
+  describe '#local?' do
+    let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) }
+
+    subject { custom_emoji.local? }
+
+    context 'domain is nil' do
+      let(:domain) { nil }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'domain is present' do
+      let(:domain) { 'example.com' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe '#object_type' do
+    it 'returns :emoji' do
+      custom_emoji = Fabricate(:custom_emoji)
+      expect(custom_emoji.object_type).to be :emoji
+    end
+  end
+
   describe '.from_text' do
     let!(:emojo) { Fabricate(:custom_emoji) }
 
diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb
index 5f5d189d9..efd2853a9 100644
--- a/spec/models/email_domain_block_spec.rb
+++ b/spec/models/email_domain_block_spec.rb
@@ -13,9 +13,10 @@ RSpec.describe EmailDomainBlock, type: :model do
       Fabricate(:email_domain_block, domain: 'example.com')
       expect(EmailDomainBlock.block?('nyarn@example.com')).to eq true
     end
+
     it 'returns true if the domain is not registed' do
-      Fabricate(:email_domain_block, domain: 'domain')
-      expect(EmailDomainBlock.block?('example')).to eq false
+      Fabricate(:email_domain_block, domain: 'example.com')
+      expect(EmailDomainBlock.block?('nyarn@example.net')).to eq false
     end
   end
 end
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index 62bd724d7..7bc93a2aa 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -2,6 +2,17 @@ require 'rails_helper'
 
 RSpec.describe FollowRequest, type: :model do
   describe '#authorize!' do
+    let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) }
+    let(:account)        { Fabricate(:account) }
+    let(:target_account) { Fabricate(:account) }
+
+    it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do
+      expect(account).to        receive(:follow!).with(target_account, reblogs: true)
+      expect(MergeWorker).to    receive(:perform_async).with(target_account.id, account.id)
+      expect(follow_request).to receive(:destroy!)
+      follow_request.authorize!
+    end
+
     it 'generates a Follow' do
       follow_request = Fabricate.create(:follow_request)
       follow_request.authorize!
@@ -23,25 +34,4 @@ RSpec.describe FollowRequest, type: :model do
       expect(follow_request.account.muting_reblogs?(target)).to be true
     end
   end
-
-  describe '#reject!'
-
-  describe 'validations' do
-    it 'has a valid fabricator' do
-      follow_request = Fabricate.build(:follow_request)
-      expect(follow_request).to be_valid
-    end
-
-    it 'is invalid without an account' do
-      follow_request = Fabricate.build(:follow_request, account: nil)
-      follow_request.valid?
-      expect(follow_request).to model_have_error_on_field(:account)
-    end
-
-    it 'is invalid without a target account' do
-      follow_request = Fabricate.build(:follow_request, target_account: nil)
-      follow_request.valid?
-      expect(follow_request).to model_have_error_on_field(:target_account)      
-    end
-  end
 end
diff --git a/spec/models/feed_spec.rb b/spec/models/home_feed_spec.rb
index 8719369db..3acb997f1 100644
--- a/spec/models/feed_spec.rb
+++ b/spec/models/home_feed_spec.rb
@@ -1,9 +1,9 @@
 require 'rails_helper'
 
-RSpec.describe Feed, type: :model do
+RSpec.describe HomeFeed, type: :model do
   let(:account) { Fabricate(:account) }
 
-  subject { described_class.new(:home, account) }
+  subject { described_class.new(account) }
 
   describe '#get' do
     before do
diff --git a/spec/models/list_account_spec.rb b/spec/models/list_account_spec.rb
new file mode 100644
index 000000000..a132e09b0
--- /dev/null
+++ b/spec/models/list_account_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe ListAccount, type: :model do
+
+end
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
new file mode 100644
index 000000000..c302482b4
--- /dev/null
+++ b/spec/models/list_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe List, type: :model do
+
+end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 435b4f326..b40a641f7 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -1,6 +1,83 @@
 require 'rails_helper'
 
 RSpec.describe MediaAttachment, type: :model do
+  describe 'local?' do
+    let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url) }
+
+    subject { media_attachment.local? }
+
+    context 'remote_url is blank' do
+      let(:remote_url) { '' }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'remote_url is present' do
+      let(:remote_url) { 'remote_url' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe 'needs_redownload?' do
+    let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url, file: file) }
+
+    subject { media_attachment.needs_redownload? }
+
+    context 'file is blank' do
+      let(:file) { nil }
+
+      context 'remote_url is blank' do
+        let(:remote_url) { '' }
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+
+      context 'remote_url is present' do
+        let(:remote_url) { 'remote_url' }
+
+        it 'returns true' do
+          is_expected.to be true
+        end
+      end
+    end
+
+    context 'file is present' do
+      let(:file) { attachment_fixture('avatar.gif') }
+
+      context 'remote_url is blank' do
+        let(:remote_url) { '' }
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+
+      context 'remote_url is present' do
+        let(:remote_url) { 'remote_url' }
+
+        it 'returns true' do
+          is_expected.to be false
+        end
+      end
+    end
+  end
+
+  describe '#to_param' do
+    let(:media_attachment) { Fabricate(:media_attachment) }
+    let(:shortcode)        { media_attachment.shortcode }
+
+    it 'returns shortcode' do
+      expect(media_attachment.to_param).to eq shortcode
+    end
+  end
+
   describe 'animated gif conversion' do
     let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) }
 
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 97e8095cd..763b1523f 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -5,6 +5,74 @@ RSpec.describe Notification, type: :model do
     pending
   end
 
+  describe '#target_status' do
+    before do
+      allow(notification).to receive(:type).and_return(type)
+      allow(notification).to receive(:activity).and_return(activity)
+    end
+
+    let(:notification) { Fabricate(:notification) }
+    let(:status)       { instance_double('Status') }
+    let(:favourite)    { instance_double('Favourite') }
+    let(:mention)      { instance_double('Mention') }
+
+    context 'type is :reblog' do
+      let(:type)     { :reblog }
+      let(:activity) { status }
+
+      it 'calls activity.reblog' do
+        expect(activity).to receive(:reblog)
+        notification.target_status
+      end
+    end
+
+    context 'type is :favourite' do
+      let(:type)     { :favourite }
+      let(:activity) { favourite }
+
+      it 'calls activity.status' do
+        expect(activity).to receive(:status)
+        notification.target_status
+      end
+    end
+
+    context 'type is :mention' do
+      let(:type)     { :mention }
+      let(:activity) { mention }
+
+      it 'calls activity.status' do
+        expect(activity).to receive(:status)
+        notification.target_status
+      end
+    end
+  end
+
+  describe '#browserable?' do
+    let(:notification) { Fabricate(:notification) }
+
+    subject { notification.browserable? }
+
+    context 'type is :follow_request' do
+      before do
+        allow(notification).to receive(:type).and_return(:follow_request)
+      end
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+
+    context 'type is not :follow_request' do
+      before do
+        allow(notification).to receive(:type).and_return(:else)
+      end
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+  end
+
   describe '#type' do
     it 'returns :reblog for a Status' do
       notification = Notification.new(activity: Status.new)
@@ -26,4 +94,49 @@ RSpec.describe Notification, type: :model do
       expect(notification.type).to eq :follow
     end
   end
+
+  describe '.reload_stale_associations!' do
+    context 'account_ids are empty' do
+      let(:cached_items) { [] }
+
+      subject { described_class.reload_stale_associations!(cached_items) }
+
+      it 'returns nil' do
+        is_expected.to be nil
+      end
+    end
+
+    context 'account_ids are present' do
+      before do
+        allow(accounts_with_ids).to receive(:[]).with(stale_account1.id).and_return(account1)
+        allow(accounts_with_ids).to receive(:[]).with(stale_account2.id).and_return(account2)
+        allow(Account).to receive_message_chain(:where, :map, :to_h).and_return(accounts_with_ids)
+      end
+
+      let(:cached_items) do
+        [
+          Fabricate(:notification, activity: Fabricate(:status)),
+          Fabricate(:notification, activity: Fabricate(:follow)),
+        ]
+      end
+
+      let(:stale_account1) { cached_items[0].from_account }
+      let(:stale_account2) { cached_items[1].from_account }
+
+      let(:account1) { Fabricate(:account) }
+      let(:account2) { Fabricate(:account) }
+
+      let(:accounts_with_ids) { { account1.id => account1, account2.id => account2 } }
+
+      it 'reloads associations' do
+        expect(cached_items[0].from_account).to be stale_account1
+        expect(cached_items[1].from_account).to be stale_account2
+
+        described_class.reload_stale_associations!(cached_items)
+
+        expect(cached_items[0].from_account).to be account1
+        expect(cached_items[1].from_account).to be account2
+      end
+    end
+  end
 end
diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb
new file mode 100644
index 000000000..72c580f9f
--- /dev/null
+++ b/spec/models/remote_follow_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe RemoteFollow do
+  before do
+    stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no').to_return(request_fixture('webfinger.txt'))
+  end
+
+  let(:attrs)         { nil }
+  let(:remote_follow) { described_class.new(attrs) }
+
+  describe '.initialize' do
+    subject { remote_follow.acct }
+
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' } }
+
+      it 'returns acct' do
+        is_expected.to eq 'gargron@quitter.no'
+      end
+    end
+
+    context 'attrs without acct' do
+      let(:attrs) { {} }
+
+      it do
+        is_expected.to be_nil
+      end
+    end
+  end
+
+  describe '#valid?' do
+    subject { remote_follow.valid? }
+
+    context 'attrs with acct' do
+      let(:attrs) { { acct: 'gargron@quitter.no' }}
+
+      it do
+        is_expected.to be true
+      end
+    end
+
+    context 'attrs without acct' do
+      let(:attrs) { { } }
+
+      it do
+        is_expected.to be false
+      end
+    end
+  end
+
+  describe '#subscribe_address_for' do
+    before do
+      remote_follow.valid?
+    end
+
+    let(:attrs)   { { acct: 'gargron@quitter.no' } }
+    let(:account) { Fabricate(:account, username: 'alice') }
+
+    subject { remote_follow.subscribe_address_for(account) }
+
+    it 'returns subscribe address' do
+      is_expected.to eq 'https://quitter.no/main/ostatussub?profile=alice%40cb6e6126.ngrok.io'
+    end
+  end
+end
diff --git a/spec/models/remote_profile_spec.rb b/spec/models/remote_profile_spec.rb
new file mode 100644
index 000000000..da5048f0a
--- /dev/null
+++ b/spec/models/remote_profile_spec.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe RemoteProfile do
+  let(:remote_profile) { RemoteProfile.new(body) }
+  let(:body) do
+    <<-XML
+      <feed xmlns="http://www.w3.org/2005/Atom">
+      <author>John</author>
+    XML
+  end
+
+  describe '.initialize' do
+    it 'calls Nokogiri::XML.parse' do
+      expect(Nokogiri::XML).to receive(:parse).with(body, nil, 'utf-8')
+      RemoteProfile.new(body)
+    end
+
+    it 'sets document' do
+      remote_profile = RemoteProfile.new(body)
+      expect(remote_profile).not_to be nil
+    end
+  end
+
+  describe '#root' do
+    let(:document) { remote_profile.document }
+
+    it 'callse document.at_xpath' do
+      expect(document).to receive(:at_xpath).with(
+        '/atom:feed|/atom:entry',
+        atom: OStatus::TagManager::XMLNS
+      )
+
+      remote_profile.root
+    end
+  end
+
+  describe '#author' do
+    let(:root) { remote_profile.root }
+
+    it 'calls root.at_xpath' do
+      expect(root).to receive(:at_xpath).with(
+        './atom:author|./dfrn:owner',
+        atom: OStatus::TagManager::XMLNS,
+        dfrn: OStatus::TagManager::DFRN_XMLNS
+      )
+
+      remote_profile.author
+    end
+  end
+
+  describe '#hub_link' do
+    let(:root) { remote_profile.root }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(root, 'hub')
+      remote_profile.hub_link
+    end
+  end
+
+  describe '#display_name' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './poco:displayName',
+        poco: OStatus::TagManager::POCO_XMLNS
+      ).with(no_args)
+
+      remote_profile.display_name
+    end
+  end
+
+  describe '#note' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './atom:summary|./poco:note',
+        atom: OStatus::TagManager::XMLNS,
+        poco: OStatus::TagManager::POCO_XMLNS
+      ).with(no_args)
+
+      remote_profile.note
+    end
+  end
+
+  describe '#scope' do
+    let(:author) { remote_profile.author }
+
+    it 'calls author.at_xpath.content' do
+      expect(author).to receive_message_chain(:at_xpath, :content).with(
+        './mastodon:scope',
+        mastodon: OStatus::TagManager::MTDN_XMLNS
+      ).with(no_args)
+
+      remote_profile.scope
+    end
+  end
+
+  describe '#avatar' do
+    let(:author) { remote_profile.author }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'avatar')
+      remote_profile.avatar
+    end
+  end
+
+  describe '#header' do
+    let(:author) { remote_profile.author }
+
+    it 'calls #link_href_from_xml' do
+      expect(remote_profile).to receive(:link_href_from_xml).with(author, 'header')
+      remote_profile.header
+    end
+  end
+
+  describe '#locked?' do
+    before do
+      allow(remote_profile).to receive(:scope).and_return(scope)
+    end
+
+    subject { remote_profile.locked? }
+
+    context 'scope is private' do
+      let(:scope) { 'private' }
+
+      it 'returns true' do
+        is_expected.to be true
+      end
+    end
+
+    context 'scope is not private' do
+      let(:scope) { 'public' }
+
+      it 'returns false' do
+        is_expected.to be false
+      end
+    end
+  end
+end
diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb
index 49c72fbd4..2aa695037 100644
--- a/spec/models/session_activation_spec.rb
+++ b/spec/models/session_activation_spec.rb
@@ -1,5 +1,127 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SessionActivation, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+  describe '#detection' do
+    let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') }
+
+    it 'sets a Browser instance as detection' do
+      expect(session_activation.detection).to be_kind_of Browser::Chrome
+    end
+  end
+
+  describe '#browser' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:detection)          { double(id: 1) }
+    let(:session_activation) { Fabricate(:session_activation) }
+
+    it 'returns detection.id' do
+      expect(session_activation.browser).to be 1
+    end
+  end
+
+  describe '#platform' do
+    before do
+      allow(session_activation).to receive(:detection).and_return(detection)
+    end
+
+    let(:session_activation) { Fabricate(:session_activation) }
+    let(:detection)          { double(platform: double(id: 1)) }
+
+    it 'returns detection.platform.id' do
+      expect(session_activation.platform).to be 1
+    end
+  end
+
+  describe '.active?' do
+    subject { described_class.active?(id) }
+
+    context 'id is absent' do
+      let(:id) { nil }
+
+      it 'returns nil' do
+        is_expected.to be nil
+      end
+    end
+
+    context 'id is present' do
+      let(:id) { '1' }
+      let!(:session_activation) { Fabricate(:session_activation, session_id: id) }
+
+      context 'id exists as session_id' do
+        it 'returns true' do
+          is_expected.to be true
+        end
+      end
+
+      context 'id does not exist as session_id' do
+        before do
+          session_activation.update!(session_id: '2')
+        end
+
+        it 'returns false' do
+          is_expected.to be false
+        end
+      end
+    end
+  end
+
+  describe '.activate' do
+    let(:options) { { user: Fabricate(:user), session_id: '1' } }
+
+    it 'calls create! and purge_old' do
+      expect(described_class).to receive(:create!).with(options)
+      expect(described_class).to receive(:purge_old)
+      described_class.activate(options)
+    end
+
+    it 'returns an instance of SessionActivation' do
+      expect(described_class.activate(options)).to be_kind_of SessionActivation
+    end
+  end
+
+  describe '.deactivate' do
+    context 'id is absent' do
+      let(:id) { nil }
+
+      it 'returns nil' do
+        expect(described_class.deactivate(id)).to be nil
+      end
+    end
+
+    context 'id exists' do
+      let(:id) { '1' }
+
+      it 'calls where.destroy_all' do
+        expect(described_class).to receive_message_chain(:where, :destroy_all)
+          .with(session_id: id).with(no_args)
+
+        described_class.deactivate(id)
+      end
+    end
+  end
+
+  describe '.purge_old' do
+    it 'calls order.offset.destroy_all' do
+      expect(described_class).to receive_message_chain(:order, :offset, :destroy_all)
+        .with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args)
+
+      described_class.purge_old
+    end
+  end
+
+  describe '.exclusive' do
+    let(:id) { '1' }
+
+    it 'calls where.destroy_all' do
+      expect(described_class).to receive_message_chain(:where, :destroy_all)
+        .with('session_id != ?', id).with(no_args)
+
+      described_class.exclusive(id)
+    end
+  end
 end
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
new file mode 100644
index 000000000..e99dfc0d7
--- /dev/null
+++ b/spec/models/setting_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Setting, type: :model do
+  describe '#to_param' do
+    let(:setting) { Fabricate(:setting, var: var) }
+    let(:var)     { 'var' }
+
+    it 'returns setting.var' do
+      expect(setting.to_param).to eq var
+    end
+  end
+
+  describe '.[]' do
+    before do
+      allow(described_class).to receive(:rails_initialized?).and_return(rails_initialized)
+    end
+
+    let(:key) { 'key' }
+
+    context 'rails_initialized? is falsey' do
+      let(:rails_initialized) { false }
+
+      it 'calls RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).to receive(:[]).with(key)
+        described_class[key]
+      end
+    end
+
+    context 'rails_initialized? is truthy' do
+      before do
+        allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key)
+      end
+
+      let(:rails_initialized) { true }
+      let(:cache_key)         { 'cache-key' }
+      let(:cache_value)       { 'cache-value' }
+
+      it 'calls not RailsSettings::Base#[]' do
+        expect(RailsSettings::Base).not_to receive(:[]).with(key)
+        described_class[key]
+      end
+
+      it 'calls Rails.cache.fetch' do
+        expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key)
+        described_class[key]
+      end
+
+      context 'Rails.cache does not exists' do
+        before do
+          allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
+          allow(described_class).to receive(:default_settings).and_return(default_settings)
+          allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+          Rails.cache.clear(cache_key)
+        end
+
+        let(:object)           { nil }
+        let(:default_value)    { 'default_value' }
+        let(:default_settings) { { key => default_value } }
+        let(:records)          { [Fabricate(:setting, var: key, value: nil)] }
+
+        it 'calls RailsSettings::Settings.object' do
+          expect(RailsSettings::Settings).to receive(:object).with(key)
+          described_class[key]
+        end
+
+        context 'RailsSettings::Settings.object returns truthy' do
+          let(:object) { db_val }
+          let(:db_val) { double(value: 'db_val') }
+
+          context 'default_value is a Hash' do
+            let(:default_value) { { default_value: 'default_value' } }
+
+            it 'calls default_value.with_indifferent_access.merge!' do
+              expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!)
+                .with(db_val.value)
+
+              described_class[key]
+            end
+          end
+
+          context 'default_value is not a Hash' do
+            let(:default_value) { 'default_value' }
+
+            it 'returns db_val.value' do
+              expect(described_class[key]).to be db_val.value
+            end
+          end
+        end
+
+        context 'RailsSettings::Settings.object returns falsey' do
+          let(:object) { nil }
+
+          it 'returns default_settings[key]' do
+            expect(described_class[key]).to be default_settings[key]
+          end
+        end
+      end
+
+      context 'Rails.cache exists' do
+        before do
+          Rails.cache.write(cache_key, cache_value)
+        end
+
+        it 'returns the cached value' do
+          expect(described_class[key]).to eq cache_value
+        end
+      end
+    end
+  end
+
+  describe '.all_as_records' do
+    before do
+      allow_any_instance_of(Settings::ScopedSettings).to receive(:thing_scoped).and_return(records)
+      allow(described_class).to receive(:default_settings).and_return(default_settings)
+    end
+
+    let(:key)              { 'key' }
+    let(:default_value)    { 'default_value' }
+    let(:default_settings) { { key => default_value } }
+    let(:original_setting) { Fabricate(:setting, var: key, value: nil) }
+    let(:records)          { [original_setting] }
+
+    it 'returns a Hash' do
+      expect(described_class.all_as_records).to be_kind_of Hash
+    end
+
+    context 'records includes Setting with var as the key' do
+      let(:records) { [original_setting] }
+
+      it 'includes the original Setting' do
+        setting = described_class.all_as_records[key]
+        expect(setting).to eq original_setting
+      end
+    end
+
+    context 'records includes nothing' do
+      let(:records) { [] }
+
+      context 'default_value is not a Hash' do
+        it 'includes Setting with value of default_value' do
+          setting = described_class.all_as_records[key]
+
+          expect(setting).to be_kind_of Setting
+          expect(setting).to have_attributes(var: key)
+          expect(setting).to have_attributes(value: 'default_value')
+        end
+      end
+
+      context 'default_value is a Hash' do
+        let(:default_value) { { 'foo' => 'fuga' } }
+
+        it 'returns {}' do
+          expect(described_class.all_as_records).to eq({})
+        end
+      end
+    end
+  end
+
+  describe '.default_settings' do
+    before do
+      allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled)
+    end
+
+    subject { described_class.default_settings }
+
+    context 'RailsSettings::Default.enabled? is false' do
+      let(:enabled) { false }
+
+      it 'returns {}' do
+        is_expected.to eq({})
+      end
+    end
+
+    context 'RailsSettings::Settings.enabled? is true' do
+      let(:enabled) { true }
+
+      it 'returns instance of RailsSettings::Default' do
+        is_expected.to be_kind_of RailsSettings::Default
+      end
+    end
+  end
+end
diff --git a/spec/models/site_upload_spec.rb b/spec/models/site_upload_spec.rb
index 8745d54b8..f7ea06921 100644
--- a/spec/models/site_upload_spec.rb
+++ b/spec/models/site_upload_spec.rb
@@ -1,5 +1,13 @@
+# frozen_string_literal: true
+
 require 'rails_helper'
 
 RSpec.describe SiteUpload, type: :model do
+  describe '#cache_key' do
+    let(:site_upload) { SiteUpload.new(var: 'var') }
 
+    it 'returns cache_key' do
+      expect(site_upload.cache_key).to eq 'site_uploads/var'
+    end
+  end
 end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 12e857169..89ad3adcf 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -47,8 +47,27 @@ RSpec.describe Status, type: :model do
   end
 
   describe '#verb' do
-    it 'is always post' do
-      expect(subject.verb).to be :post
+    context 'if destroyed?' do
+      it 'returns :delete' do
+        subject.destroy!
+        expect(subject.verb).to be :delete
+      end
+    end
+
+    context 'unless destroyed?' do
+      context 'if reblog?' do
+        it 'returns :share' do
+          subject.reblog = other
+          expect(subject.verb).to be :share
+        end
+      end
+
+      context 'unless reblog?' do
+        it 'returns :post' do
+          subject.reblog = nil
+          expect(subject.verb).to be :post
+        end
+      end
     end
   end
 
@@ -69,6 +88,36 @@ RSpec.describe Status, type: :model do
     end
   end
 
+  describe '#hidden?' do
+    context 'if private_visibility?' do
+      it 'returns true' do
+        subject.visibility = :private
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if direct_visibility?' do
+      it 'returns true' do
+        subject.visibility = :direct
+        expect(subject.hidden?).to be true
+      end
+    end
+
+    context 'if public_visibility?' do
+      it 'returns false' do
+        subject.visibility = :public
+        expect(subject.hidden?).to be false
+      end
+    end
+
+    context 'if unlisted_visibility?' do
+      it 'returns false' do
+        subject.visibility = :unlisted
+        expect(subject.hidden?).to be false
+      end
+    end
+  end
+
   describe '#content' do
     it 'returns the text of the status if it is not a reblog' do
       expect(subject.content).to eql subject.text
diff --git a/spec/models/stream_entry_spec.rb b/spec/models/stream_entry_spec.rb
index 3b7ff5143..8f8bfbd58 100644
--- a/spec/models/stream_entry_spec.rb
+++ b/spec/models/stream_entry_spec.rb
@@ -6,6 +6,121 @@ RSpec.describe StreamEntry, type: :model do
   let(:status)    { Fabricate(:status, account: alice) }
   let(:reblog)    { Fabricate(:status, account: bob, reblog: status) }
   let(:reply)     { Fabricate(:status, account: bob, thread: status) }
+  let(:stream_entry) { Fabricate(:stream_entry, activity: activity) }
+  let(:activity)     { reblog }
+
+  describe '#object_type' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+      allow(stream_entry).to receive(:targeted?).and_return(targeted)
+    end
+
+    subject { stream_entry.object_type }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+      let(:targeted) { false }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'targeted? is true' do
+      let(:orphaned) { false }
+      let(:targeted) { true }
+
+      it 'returns :activity' do
+        is_expected.to be :activity
+      end
+    end
+
+    context 'orphaned? and targeted? are false' do
+      let(:orphaned) { false }
+      let(:targeted) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :note' do
+          is_expected.to be :note
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :comment' do
+          is_expected.to be :comment
+        end
+      end
+    end
+  end
+
+  describe '#verb' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.verb }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns :delete' do
+        is_expected.to be :delete
+      end
+    end
+
+    context 'orphaned? is false' do
+      let(:orphaned) { false }
+
+      context 'activity is reblog' do
+        let(:activity) { reblog }
+
+        it 'returns :share' do
+          is_expected.to be :share
+        end
+      end
+
+      context 'activity is reply' do
+        let(:activity) { reply }
+
+        it 'returns :post' do
+          is_expected.to be :post
+        end
+      end
+    end
+  end
+
+  describe '#mentions' do
+    before do
+      allow(stream_entry).to receive(:orphaned?).and_return(orphaned)
+    end
+
+    subject { stream_entry.mentions }
+
+    context 'orphaned? is true' do
+      let(:orphaned) { true }
+
+      it 'returns []' do
+        is_expected.to eq []
+      end
+    end
+
+    context 'orphaned? is false' do
+      before do
+        reblog.mentions << Fabricate(:mention, account: alice)
+        reblog.mentions << Fabricate(:mention, account: bob)
+      end
+
+      let(:orphaned) { false }
+
+      it 'returns [Account] includes alice and bob' do
+        is_expected.to eq [alice, bob]
+      end
+    end
+  end
 
   describe '#targeted?' do
     it 'returns true for a reblog' do
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index f727fa1dd..1ca50cc29 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -35,6 +35,13 @@ RSpec.describe Tag, type: :model do
     end
   end
 
+  describe '#to_param' do
+    it 'returns name' do
+      tag = Fabricate(:tag, name: 'foo')
+      expect(tag.to_param).to eq 'foo'
+    end
+  end
+
   describe '.search_for' do
     it 'finds tag records with matching names' do
       tag = Fabricate(:tag, name: "match")
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 99aeca01b..77a12c26d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -177,27 +177,10 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_auto_play_gif' do
-    it 'returns auto-play gif setting' do
+  describe 'settings' do
+    it 'is instance of Settings::ScopedSettings' do
       user = Fabricate(:user)
-      user.settings[:auto_play_gif] = false
-      expect(user.setting_auto_play_gif).to eq false
-    end
-  end
-  
-  describe '#setting_system_font_ui' do
-    it 'returns system font ui setting' do
-      user = Fabricate(:user)
-      user.settings[:system_font_ui] = false
-      expect(user.setting_system_font_ui).to eq false
-    end
-  end
-
-  describe '#setting_boost_modal' do
-    it 'returns boost modal setting' do
-      user = Fabricate(:user)
-      user.settings[:boost_modal] = false
-      expect(user.setting_boost_modal).to eq false
+      expect(user.settings).to be_kind_of Settings::ScopedSettings
     end
   end
 
@@ -219,22 +202,6 @@ RSpec.describe User, type: :model do
     end
   end
 
-  describe '#setting_unfollow_modal' do
-    it 'returns unfollow modal setting' do
-      user = Fabricate(:user)
-      user.settings[:unfollow_modal] = true
-      expect(user.setting_unfollow_modal).to eq true
-    end
-  end
-
-  describe '#setting_delete_modal' do
-    it 'returns delete modal setting' do
-      user = Fabricate(:user)
-      user.settings[:delete_modal] = false
-      expect(user.setting_delete_modal).to eq false
-    end
-  end
-
   describe 'whitelist' do
     around(:each) do |example|
       old_whitelist = Rails.configuration.x.email_whitelist
diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb
index bacb8fd9e..a90e22aad 100644
--- a/spec/policies/status_policy_spec.rb
+++ b/spec/policies/status_policy_spec.rb
@@ -71,6 +71,12 @@ RSpec.describe StatusPolicy, type: :model do
 
       expect(subject).to_not permit(viewer, status)
     end
+
+    it 'denies access when local-only and the viewer is not logged in' do
+      allow(status).to receive(:local_only?) { true }
+
+      expect(subject).to_not permit(nil, status)
+    end
   end
 
   permissions :reblog? do
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index ebf422392..51f3fe3a1 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService do
 
       it 'creates status' do
         status = sender.statuses.first
-        
+
         expect(status).to_not be_nil
         expect(status.text).to eq 'Lorem ipsum'
       end
diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb
index 65f36c7d1..1b115c938 100644
--- a/spec/services/after_block_service_spec.rb
+++ b/spec/services/after_block_service_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe AfterBlockService do
     end
 
     it "clears account's statuses" do
-      FeedManager.instance.push(:home, account, status)
-      FeedManager.instance.push(:home, account, other_account_status)
+      FeedManager.instance.push_to_home(account, status)
+      FeedManager.instance.push_to_home(account, other_account_status)
 
       is_expected.to change {
         Redis.current.zrange(home_timeline_key, 0, -1)
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index c82c45e09..437da2a9d 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -30,11 +30,11 @@ RSpec.describe BatchedRemoveStatusService do
   end
 
   it 'removes statuses from author\'s home feed' do
-    expect(Feed.new(:home, alice).get(10)).to_not include([status1.id, status2.id])
+    expect(HomeFeed.new(alice).get(10)).to_not include([status1.id, status2.id])
   end
 
   it 'removes statuses from local follower\'s home feed' do
-    expect(Feed.new(:home, jeff).get(10)).to_not include([status1.id, status2.id])
+    expect(HomeFeed.new(jeff).get(10)).to_not include([status1.id, status2.id])
   end
 
   it 'notifies streaming API of followers' do
diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb
index 6ee225c4c..764318e34 100644
--- a/spec/services/fan_out_on_write_service_spec.rb
+++ b/spec/services/fan_out_on_write_service_spec.rb
@@ -19,12 +19,12 @@ RSpec.describe FanOutOnWriteService do
   end
 
   it 'delivers status to home timeline' do
-    expect(Feed.new(:home, author).get(10).map(&:id)).to include status.id
+    expect(HomeFeed.new(author).get(10).map(&:id)).to include status.id
   end
 
   it 'delivers status to local followers' do
     pending 'some sort of problem in test environment causes this to sometimes fail'
-    expect(Feed.new(:home, follower).get(10).map(&:id)).to include status.id
+    expect(HomeFeed.new(follower).get(10).map(&:id)).to include status.id
   end
 
   it 'delivers status to hashtag' do
diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb
index 800140b6f..2b3e3e152 100644
--- a/spec/services/mute_service_spec.rb
+++ b/spec/services/mute_service_spec.rb
@@ -18,8 +18,8 @@ RSpec.describe MuteService do
     end
 
     it "clears account's statuses" do
-      FeedManager.instance.push(:home, account, status)
-      FeedManager.instance.push(:home, account, other_account_status)
+      FeedManager.instance.push_to_home(account, status)
+      FeedManager.instance.push_to_home(account, other_account_status)
 
       is_expected.to change {
         Redis.current.zrange(home_timeline_key, 0, -1)
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index 250a880a2..a8ebc16b8 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -67,6 +67,39 @@ RSpec.describe NotifyService do
       is_expected.to_not change(Notification, :count)
     end
   end
+  
+  context 'for direct messages' do
+    let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) }
+
+    before do
+      user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled)
+    end
+
+    context 'if recipient is supposed to be following sender' do
+      let(:enabled) { true }
+
+      it 'does not notify' do
+        is_expected.to_not change(Notification, :count)
+      end
+
+      context 'if the message chain initiated by recipient' do
+        let(:reply_to) { Fabricate(:status, account: recipient) }
+        let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
+
+        it 'does notify' do
+          is_expected.to change(Notification, :count)
+        end
+      end
+    end
+
+    context 'if recipient is NOT supposed to be following sender' do
+      let(:enabled) { false }
+
+      it 'does notify' do
+        is_expected.to change(Notification, :count)
+      end
+    end
+  end
 
   context do
     let(:asshole)  { Fabricate(:account, username: 'asshole') }
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index b60015928..5bb75b820 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -25,11 +25,11 @@ RSpec.describe RemoveStatusService do
   end
 
   it 'removes status from author\'s home feed' do
-    expect(Feed.new(:home, alice).get(10)).to_not include(@status.id)
+    expect(HomeFeed.new(alice).get(10)).to_not include(@status.id)
   end
 
   it 'removes status from local follower\'s home feed' do
-    expect(Feed.new(:home, jeff).get(10)).to_not include(@status.id)
+    expect(HomeFeed.new(jeff).get(10)).to_not include(@status.id)
   end
 
   it 'sends PuSH update to PuSH subscribers' do
diff --git a/spec/support/matchers/model/model_have_error_on_field.rb b/spec/support/matchers/model/model_have_error_on_field.rb
index 5d5fe1c7b..a5dfbf457 100644
--- a/spec/support/matchers/model/model_have_error_on_field.rb
+++ b/spec/support/matchers/model/model_have_error_on_field.rb
@@ -9,7 +9,7 @@ RSpec::Matchers.define :model_have_error_on_field do |expected|
 
   failure_message do |record|
     keys = record.errors.keys
-    
+
     "expect record.errors(#{keys}) to include #{expected}"
   end
 end
diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb
index 71a3dea00..3509f1f50 100644
--- a/spec/workers/feed_insert_worker_spec.rb
+++ b/spec/workers/feed_insert_worker_spec.rb
@@ -11,41 +11,41 @@ describe FeedInsertWorker do
 
     context 'when there are no records' do
       it 'skips push with missing status' do
-        instance = double(push: nil)
+        instance = double(push_to_home: nil)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(nil, follower.id)
 
         expect(result).to eq true
-        expect(instance).not_to have_received(:push)
+        expect(instance).not_to have_received(:push_to_home)
       end
 
       it 'skips push with missing account' do
-        instance = double(push: nil)
+        instance = double(push_to_home: nil)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, nil)
 
         expect(result).to eq true
-        expect(instance).not_to have_received(:push)
+        expect(instance).not_to have_received(:push_to_home)
       end
     end
 
     context 'when there are real records' do
       it 'skips the push when there is a filter' do
-        instance = double(push: nil, filter?: true)
+        instance = double(push_to_home: nil, filter?: true)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, follower.id)
 
         expect(result).to be_nil
-        expect(instance).not_to have_received(:push)
+        expect(instance).not_to have_received(:push_to_home)
       end
 
       it 'pushes the status onto the home timeline without filter' do
-        instance = double(push: nil, filter?: false)
+        instance = double(push_to_home: nil, filter?: false)
         allow(FeedManager).to receive(:instance).and_return(instance)
         result = subject.perform(status.id, follower.id)
 
         expect(result).to be_nil
-        expect(instance).to have_received(:push).with(:home, follower, status)
+        expect(instance).to have_received(:push_to_home).with(follower, status)
       end
     end
   end
diff --git a/streaming/index.js b/streaming/index.js
index 8adc5174a..3048802e3 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -254,6 +254,26 @@ const startWorker = (workerId) => {
 
   const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', ');
 
+  const authorizeListAccess = (id, req, next) => {
+    pgPool.connect((err, client, done) => {
+      if (err) {
+        next(false);
+        return;
+      }
+
+      client.query('SELECT id, account_id FROM lists WHERE id = $1 LIMIT 1', [id], (err, result) => {
+        done();
+
+        if (err || result.rows.length === 0 || result.rows[0].account_id !== req.accountId) {
+          next(false);
+          return;
+        }
+
+        next(true);
+      });
+    });
+  };
+
   const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false, notificationOnly = false) => {
     const streamType = notificationOnly ? ' (notification)' : '';
     log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}${streamType}`);
@@ -414,7 +434,22 @@ const startWorker = (workerId) => {
     streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}:local`, req, streamToHttp(req, res), streamHttpEnd(req), true);
   });
 
-  const wss    = new WebSocket.Server({ server, verifyClient: wsVerifyClient });
+  app.get('/api/v1/streaming/list', (req, res) => {
+    const listId = req.query.list;
+
+    authorizeListAccess(listId, req, authorized => {
+      if (!authorized) {
+        res.writeHead(404, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ error: 'Not found' }));
+        return;
+      }
+
+      const channel = `timeline:list:${listId}`;
+      streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)));
+    });
+  });
+
+  const wss = new WebSocket.Server({ server, verifyClient: wsVerifyClient });
 
   wss.on('connection', ws => {
     const req      = ws.upgradeReq;
@@ -450,6 +485,19 @@ const startWorker = (workerId) => {
     case 'hashtag:local':
       streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}:local`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
       break;
+    case 'list':
+      const listId = location.query.list;
+
+      authorizeListAccess(listId, req, authorized => {
+        if (!authorized) {
+          ws.close();
+          return;
+        }
+
+        const channel = `timeline:list:${listId}`;
+        streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)));
+      });
+      break;
     default:
       ws.close();
     }
@@ -467,7 +515,7 @@ const startWorker = (workerId) => {
     });
   }, 30000);
 
-  server.listen(process.env.PORT || 4000, () => {
+  server.listen(process.env.PORT || 4000, process.env.BIND || '0.0.0.0', () => {
     log.info(`Worker ${workerId} now listening on ${server.address().address}:${server.address().port}`);
   });
 
diff --git a/yarn.lock b/yarn.lock
index aceff463b..0805e8af3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3,23 +3,16 @@
 
 
 "@types/node@^6.0.46":
-  version "6.0.89"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.89.tgz#154be0e6a823760cd6083aa8c48f952e2e63e0b0"
+  version "6.0.90"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.90.tgz#0ed74833fa1b73dcdb9409dcb1c97ec0a8b13b02"
 
 abab@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
 
 abbrev@1:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
-
-accepts@~1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
-  dependencies:
-    mime-types "~2.1.11"
-    negotiator "0.6.1"
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
 
 accepts@~1.3.4:
   version "1.3.4"
@@ -54,9 +47,9 @@ acorn@^4.0.3, acorn@^4.0.4:
   version "4.0.13"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
 
-acorn@^5.0.0, acorn@^5.0.1, acorn@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
+acorn@^5.0.0, acorn@^5.1.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7"
 
 adjust-sourcemap-loader@^1.1.0:
   version "1.1.0"
@@ -75,8 +68,8 @@ ajv-keywords@^1.0.0:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
 
 ajv-keywords@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
 
 ajv@^4.7.0, ajv@^4.9.1:
   version "4.11.8"
@@ -85,14 +78,14 @@ ajv@^4.7.0, ajv@^4.9.1:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^5.0.0, ajv@^5.1.5:
-  version "5.2.2"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39"
+ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
   dependencies:
     co "^4.6.0"
     fast-deep-equal "^1.0.0"
+    fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.3.0"
-    json-stable-stringify "^1.0.1"
 
 align-text@^0.1.1, align-text@^0.1.3:
   version "0.1.4"
@@ -134,13 +127,7 @@ ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
 
-ansi-styles@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750"
-  dependencies:
-    color-convert "^1.0.0"
-
-ansi-styles@^3.2.0:
+ansi-styles@^3.1.0, ansi-styles@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
   dependencies:
@@ -151,15 +138,11 @@ any-promise@^0.1.0:
   resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27"
 
 anymatch@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
   dependencies:
-    arrify "^1.0.0"
     micromatch "^2.1.5"
-
-ap@~0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110"
+    normalize-path "^2.0.0"
 
 append-transform@^0.4.0:
   version "0.4.0"
@@ -168,8 +151,8 @@ append-transform@^0.4.0:
     default-require-extensions "^1.0.0"
 
 aproba@^1.0.3:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1"
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
 
 are-we-there-yet@~1.1.2:
   version "1.1.4"
@@ -294,15 +277,17 @@ async-foreach@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
 
-async@0.2.x:
-  version "0.2.10"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+async@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7"
+  dependencies:
+    lodash "^4.14.0"
 
 async@^1.4.0, async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
-async@^2.1.2, async@^2.1.4, async@^2.1.5:
+async@^2.1.2, async@^2.1.4, async@^2.1.5, async@^2.4.1:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
   dependencies:
@@ -331,41 +316,45 @@ autoprefixer@^6.3.1:
     postcss "^5.2.16"
     postcss-value-parser "^3.2.3"
 
-autoprefixer@^7.1.2:
-  version "7.1.4"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748"
+autoprefixer@^7.1.6:
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.6.tgz#fb933039f74af74a83e71225ce78d9fd58ba84d7"
   dependencies:
-    browserslist "^2.4.0"
-    caniuse-lite "^1.0.30000726"
+    browserslist "^2.5.1"
+    caniuse-lite "^1.0.30000748"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
-    postcss "^6.0.11"
+    postcss "^6.0.13"
     postcss-value-parser "^3.2.3"
 
 aws-sign2@~0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
 
-aws4@^1.2.1:
+aws-sign2@~0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.2.1, aws4@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
 
-axios@^0.16.2:
+axios@~0.16.2:
   version "0.16.2"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
   dependencies:
     follow-redirects "^1.2.3"
     is-buffer "^1.1.5"
 
-babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
-  version "6.22.0"
-  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
+babel-code-frame@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-7.0.0-beta.0.tgz#418a7b5f3f7dc9a4670e61b1158b4c5661bec98d"
   dependencies:
-    chalk "^1.1.0"
+    chalk "^2.0.0"
     esutils "^2.0.2"
     js-tokens "^3.0.0"
 
-babel-code-frame@^6.26.0:
+babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
   dependencies:
@@ -406,6 +395,15 @@ babel-eslint@^7.2.3:
     babel-types "^6.23.0"
     babylon "^6.17.0"
 
+babel-eslint@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.1.tgz#5d718be7a328625d006022eb293ed3008cbd6346"
+  dependencies:
+    babel-code-frame "7.0.0-beta.0"
+    babel-traverse "7.0.0-beta.0"
+    babel-types "7.0.0-beta.0"
+    babylon "7.0.0-beta.22"
+
 babel-generator@^6.18.0, babel-generator@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
@@ -428,12 +426,12 @@ babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
     babel-types "^6.24.1"
 
 babel-helper-builder-react-jsx@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.24.1.tgz#0ad7917e33c8d751e646daca4e77cc19377d2cbc"
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0"
   dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-    esutils "^2.0.0"
+    babel-runtime "^6.26.0"
+    babel-types "^6.26.0"
+    esutils "^2.0.2"
 
 babel-helper-call-delegate@^6.24.1:
   version "6.24.1"
@@ -445,13 +443,13 @@ babel-helper-call-delegate@^6.24.1:
     babel-types "^6.24.1"
 
 babel-helper-define-map@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080"
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f"
   dependencies:
     babel-helper-function-name "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
+    babel-runtime "^6.26.0"
+    babel-types "^6.26.0"
+    lodash "^4.17.4"
 
 babel-helper-explode-assignable-expression@^6.24.1:
   version "6.24.1"
@@ -461,6 +459,15 @@ babel-helper-explode-assignable-expression@^6.24.1:
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
+babel-helper-function-name@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-7.0.0-beta.0.tgz#d1b6779b647e5c5c31ebeb05e13b998e4d352d56"
+  dependencies:
+    babel-helper-get-function-arity "7.0.0-beta.0"
+    babel-template "7.0.0-beta.0"
+    babel-traverse "7.0.0-beta.0"
+    babel-types "7.0.0-beta.0"
+
 babel-helper-function-name@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
@@ -471,6 +478,12 @@ babel-helper-function-name@^6.24.1:
     babel-traverse "^6.24.1"
     babel-types "^6.24.1"
 
+babel-helper-get-function-arity@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-7.0.0-beta.0.tgz#9d1ab7213bb5efe1ef1638a8ea1489969b5a8b6e"
+  dependencies:
+    babel-types "7.0.0-beta.0"
+
 babel-helper-get-function-arity@^6.24.1:
   version "6.24.1"
   resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d"
@@ -493,12 +506,12 @@ babel-helper-optimise-call-expression@^6.24.1:
     babel-types "^6.24.1"
 
 babel-helper-regex@^6.24.1:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8"
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72"
   dependencies:
-    babel-runtime "^6.22.0"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
+    babel-runtime "^6.26.0"
+    babel-types "^6.26.0"
+    lodash "^4.17.4"
 
 babel-helper-remap-async-to-generator@^6.24.1:
   version "6.24.1"
@@ -543,9 +556,15 @@ babel-loader@^7.1.1:
     loader-utils "^1.0.2"
     mkdirp "^0.5.1"
 
-babel-macros@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/babel-macros/-/babel-macros-1.0.2.tgz#04475889990243cc58a0afb5ea3308ec6b89e797"
+babel-macros@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/babel-macros/-/babel-macros-1.2.0.tgz#39e47ed6d286d4a98f1948d8bab45dac17e4e2d4"
+  dependencies:
+    cosmiconfig "3.1.0"
+
+babel-messages@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-7.0.0-beta.0.tgz#6df01296e49fc8fbd0637394326a167f36da817b"
 
 babel-messages@^6.23.0:
   version "6.23.0"
@@ -578,15 +597,15 @@ babel-plugin-lodash@^3.2.11:
     glob "^7.1.1"
     lodash "^4.17.2"
 
-babel-plugin-preval@^1.3.2:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-preval/-/babel-plugin-preval-1.5.0.tgz#be4e3353ce6ec4fd0c6b199701193306033bf54b"
+babel-plugin-preval@^1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-preval/-/babel-plugin-preval-1.6.1.tgz#c33fd124f9cb9281550cae35ff0ea6065e32261d"
   dependencies:
     babel-core "^6.26.0"
-    babel-macros "^1.0.0"
+    babel-macros "^1.1.1"
     babel-register "^6.26.0"
     babylon "^6.18.0"
-    require-from-string "^1.2.1"
+    require-from-string "^2.0.1"
 
 babel-plugin-react-intl@^2.3.1:
   version "2.3.1"
@@ -596,12 +615,6 @@ babel-plugin-react-intl@^2.3.1:
     intl-messageformat-parser "^1.2.0"
     mkdirp "^0.5.1"
 
-babel-plugin-react-transform@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.2.tgz#515bbfa996893981142d90b1f9b1635de2995109"
-  dependencies:
-    lodash "^4.6.1"
-
 babel-plugin-syntax-async-functions@^6.8.0:
   version "6.13.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -676,14 +689,14 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
     babel-runtime "^6.22.0"
 
 babel-plugin-transform-es2015-block-scoping@^6.23.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576"
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
   dependencies:
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-traverse "^6.24.1"
-    babel-types "^6.24.1"
-    lodash "^4.2.0"
+    babel-runtime "^6.26.0"
+    babel-template "^6.26.0"
+    babel-traverse "^6.26.0"
+    babel-types "^6.26.0"
+    lodash "^4.17.4"
 
 babel-plugin-transform-es2015-classes@^6.23.0:
   version "6.24.1"
@@ -747,16 +760,7 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015
     babel-runtime "^6.22.0"
     babel-template "^6.24.1"
 
-babel-plugin-transform-es2015-modules-commonjs@^6.23.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe"
-  dependencies:
-    babel-plugin-transform-strict-mode "^6.24.1"
-    babel-runtime "^6.22.0"
-    babel-template "^6.24.1"
-    babel-types "^6.24.1"
-
-babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
   dependencies:
@@ -896,17 +900,15 @@ babel-plugin-transform-react-jsx@^6.24.1:
     babel-plugin-syntax-jsx "^6.8.0"
     babel-runtime "^6.22.0"
 
-babel-plugin-transform-react-remove-prop-types@^0.4.6:
-  version "0.4.8"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.8.tgz#0aff04bc1d6564ec49cf23bcffb99c11881958db"
-  dependencies:
-    babel-traverse "^6.25.0"
+babel-plugin-transform-react-remove-prop-types@^0.4.10:
+  version "0.4.10"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.10.tgz#3c7f3a03ad8aa6bb8c00e93fd13a433910444545"
 
 babel-plugin-transform-regenerator@^6.22.0:
-  version "6.24.1"
-  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418"
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
   dependencies:
-    regenerator-transform "0.9.11"
+    regenerator-transform "^0.10.0"
 
 babel-plugin-transform-runtime@^6.23.0:
   version "6.23.0"
@@ -921,9 +923,9 @@ babel-plugin-transform-strict-mode@^6.24.1:
     babel-runtime "^6.22.0"
     babel-types "^6.24.1"
 
-babel-preset-env@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4"
+babel-preset-env@^1.6.1:
+  version "1.6.1"
+  resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48"
   dependencies:
     babel-plugin-check-es2015-constants "^6.22.0"
     babel-plugin-syntax-trailing-function-commas "^6.22.0"
@@ -992,21 +994,23 @@ babel-register@^6.26.0:
     mkdirp "^0.5.1"
     source-map-support "^0.4.15"
 
-babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0:
-  version "6.23.0"
-  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b"
-  dependencies:
-    core-js "^2.4.0"
-    regenerator-runtime "^0.10.0"
-
-babel-runtime@^6.26.0:
+babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
   dependencies:
     core-js "^2.4.0"
     regenerator-runtime "^0.11.0"
 
-babel-template@^6.16.0, babel-template@^6.26.0:
+babel-template@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-7.0.0-beta.0.tgz#85083cf9e4395d5e48bf5154d7a8d6991cafecfb"
+  dependencies:
+    babel-traverse "7.0.0-beta.0"
+    babel-types "7.0.0-beta.0"
+    babylon "7.0.0-beta.22"
+    lodash "^4.2.0"
+
+babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
   dependencies:
@@ -1016,17 +1020,21 @@ babel-template@^6.16.0, babel-template@^6.26.0:
     babylon "^6.18.0"
     lodash "^4.17.4"
 
-babel-template@^6.24.1, babel-template@^6.3.0:
-  version "6.25.0"
-  resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071"
-  dependencies:
-    babel-runtime "^6.22.0"
-    babel-traverse "^6.25.0"
-    babel-types "^6.25.0"
-    babylon "^6.17.2"
+babel-traverse@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-7.0.0-beta.0.tgz#da14be9b762f62a2f060db464eaafdd8cd072a41"
+  dependencies:
+    babel-code-frame "7.0.0-beta.0"
+    babel-helper-function-name "7.0.0-beta.0"
+    babel-messages "7.0.0-beta.0"
+    babel-types "7.0.0-beta.0"
+    babylon "7.0.0-beta.22"
+    debug "^3.0.1"
+    globals "^10.0.0"
+    invariant "^2.2.0"
     lodash "^4.2.0"
 
-babel-traverse@^6.18.0, babel-traverse@^6.26.0:
+babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
   dependencies:
@@ -1040,21 +1048,15 @@ babel-traverse@^6.18.0, babel-traverse@^6.26.0:
     invariant "^2.2.2"
     lodash "^4.17.4"
 
-babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0:
-  version "6.25.0"
-  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1"
+babel-types@7.0.0-beta.0:
+  version "7.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-7.0.0-beta.0.tgz#eb8b6e556470e6dcc4aef982d79ad229469b5169"
   dependencies:
-    babel-code-frame "^6.22.0"
-    babel-messages "^6.23.0"
-    babel-runtime "^6.22.0"
-    babel-types "^6.25.0"
-    babylon "^6.17.2"
-    debug "^2.2.0"
-    globals "^9.0.0"
-    invariant "^2.2.0"
+    esutils "^2.0.2"
     lodash "^4.2.0"
+    to-fast-properties "^2.0.0"
 
-babel-types@^6.18.0, babel-types@^6.26.0:
+babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
   dependencies:
@@ -1063,20 +1065,11 @@ babel-types@^6.18.0, babel-types@^6.26.0:
     lodash "^4.17.4"
     to-fast-properties "^1.0.3"
 
-babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0:
-  version "6.25.0"
-  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e"
-  dependencies:
-    babel-runtime "^6.22.0"
-    esutils "^2.0.2"
-    lodash "^4.2.0"
-    to-fast-properties "^1.0.1"
-
-babylon@^6.17.0, babylon@^6.17.2:
-  version "6.17.4"
-  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a"
+babylon@7.0.0-beta.22:
+  version "7.0.0-beta.22"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.22.tgz#74f0ad82ed7c7c3cfeab74cf684f815104161b65"
 
-babylon@^6.18.0:
+babylon@^6.17.0, babylon@^6.18.0:
   version "6.18.0"
   resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
 
@@ -1113,12 +1106,12 @@ bcrypt-pbkdf@^1.0.0:
     tweetnacl "^0.14.3"
 
 big.js@^3.1.3:
-  version "3.1.3"
-  resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
 
 binary-extensions@^1.0.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774"
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0"
 
 block-stream@*:
   version "0.0.9"
@@ -1127,8 +1120,8 @@ block-stream@*:
     inherits "~2.0.0"
 
 bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
-  version "4.11.7"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
+  version "4.11.8"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
 
 body-parser@1.18.2:
   version "1.18.2"
@@ -1166,6 +1159,18 @@ boom@2.x.x:
   dependencies:
     hoek "2.x.x"
 
+boom@4.x.x:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+  dependencies:
+    hoek "4.x.x"
+
+boom@5.x.x:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+  dependencies:
+    hoek "4.x.x"
+
 brace-expansion@^1.1.7:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@@ -1192,14 +1197,15 @@ browser-resolve@^1.11.2:
     resolve "1.1.7"
 
 browserify-aes@^1.0.0, browserify-aes@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f"
   dependencies:
-    buffer-xor "^1.0.2"
+    buffer-xor "^1.0.3"
     cipher-base "^1.0.0"
     create-hash "^1.1.0"
-    evp_bytestokey "^1.0.0"
+    evp_bytestokey "^1.0.3"
     inherits "^2.0.1"
+    safe-buffer "^5.0.1"
 
 browserify-cipher@^1.0.0:
   version "1.0.0"
@@ -1249,19 +1255,12 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
     caniuse-db "^1.0.30000639"
     electron-to-chromium "^1.2.7"
 
-browserslist@^2.1.2:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.1.5.tgz#e882550df3d1cd6d481c1a3e0038f2baf13a4711"
-  dependencies:
-    caniuse-lite "^1.0.30000684"
-    electron-to-chromium "^1.3.14"
-
-browserslist@^2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8"
+browserslist@^2.1.2, browserslist@^2.5.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.6.1.tgz#cc65a05ad6131ebda26f076f2822ba1bc826376b"
   dependencies:
-    caniuse-lite "^1.0.30000718"
-    electron-to-chromium "^1.3.18"
+    caniuse-lite "^1.0.30000755"
+    electron-to-chromium "^1.3.27"
 
 bser@^2.0.0:
   version "2.0.0"
@@ -1270,14 +1269,14 @@ bser@^2.0.0:
     node-int64 "^0.4.0"
 
 buffer-indexof@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982"
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
 
 buffer-writer@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08"
 
-buffer-xor@^1.0.2:
+buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
 
@@ -1297,10 +1296,6 @@ builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
 
-bytes@2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a"
-
 bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -1356,16 +1351,12 @@ caniuse-api@^1.5.2:
     lodash.uniq "^4.5.0"
 
 caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
-  version "1.0.30000700"
-  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000700.tgz#97cfc483865eea8577dc7a3674929b9abf553095"
+  version "1.0.30000755"
+  resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000755.tgz#a08c547c39dbe4ad07dcca9763fcbbff0c891de0"
 
-caniuse-lite@^1.0.30000684:
-  version "1.0.30000700"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000700.tgz#6084871ec75c6fa62327de97622514f95d9db26a"
-
-caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726:
-  version "1.0.30000740"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973"
+caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000755:
+  version "1.0.30000755"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000755.tgz#9ce5f6e06bd75ec8209abe8853c3beef02248d65"
 
 caseless@~0.12.0:
   version "0.12.0"
@@ -1382,7 +1373,7 @@ chain-function@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
 
-chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
   dependencies:
@@ -1392,17 +1383,9 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
     strip-ansi "^3.0.0"
     supports-color "^2.0.0"
 
-chalk@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
-  dependencies:
-    ansi-styles "^3.1.0"
-    escape-string-regexp "^1.0.5"
-    supports-color "^4.0.0"
-
-chalk@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
   dependencies:
     ansi-styles "^3.1.0"
     escape-string-regexp "^1.0.5"
@@ -1446,12 +1429,12 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
     safe-buffer "^5.0.1"
 
 circular-json@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
 
 clap@^1.0.9:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857"
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
   dependencies:
     chalk "^1.1.3"
 
@@ -1466,8 +1449,8 @@ cli-cursor@^1.0.1:
     restore-cursor "^1.0.1"
 
 cli-width@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
 
 cliui@^2.1.0:
   version "2.1.0"
@@ -1512,15 +1495,15 @@ code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
 
-color-convert@^1.0.0, color-convert@^1.3.0, color-convert@^1.9.0:
+color-convert@^1.3.0, color-convert@^1.9.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
   dependencies:
     color-name "^1.1.1"
 
 color-name@^1.0.0, color-name@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d"
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
 
 color-string@^0.3.0:
   version "0.3.0"
@@ -1570,32 +1553,30 @@ complex.js@2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.4.tgz#d8e7cfb9652d1e853e723386421c1a0ca7a48373"
 
-compressible@~2.0.10:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd"
+compressible@~2.0.11:
+  version "2.0.12"
+  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.12.tgz#c59a5c99db76767e9876500e271ef63b3493bd66"
   dependencies:
-    mime-db ">= 1.27.0 < 2"
+    mime-db ">= 1.30.0 < 2"
 
-compression-webpack-plugin@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-0.4.0.tgz#811de04215f811ea6a12d4d8aed8457d758f13ac"
+compression-webpack-plugin@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.0.1.tgz#7f0a2af9f642b4f87b5989516a3b9e9b41bb4b3f"
   dependencies:
-    async "0.2.x"
-    webpack-sources "^0.1.0"
-  optionalDependencies:
-    node-zopfli "^2.0.0"
+    async "2.4.1"
+    webpack-sources "^1.0.1"
 
 compression@^1.5.2:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d"
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.1.tgz#eff2603efc2e22cf86f35d2eb93589f9875373db"
   dependencies:
-    accepts "~1.3.3"
-    bytes "2.5.0"
-    compressible "~2.0.10"
-    debug "2.6.8"
+    accepts "~1.3.4"
+    bytes "3.0.0"
+    compressible "~2.0.11"
+    debug "2.6.9"
     on-headers "~1.0.1"
     safe-buffer "5.1.1"
-    vary "~1.1.1"
+    vary "~1.1.2"
 
 concat-map@0.0.1:
   version "0.0.1"
@@ -1610,8 +1591,8 @@ concat-stream@^1.5.2:
     typedarray "^0.0.6"
 
 connect-history-api-fallback@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.4.0.tgz#3db24f973f4b923b0e82f619ce0df02411ca623d"
 
 console-browserify@^1.1.0:
   version "1.1.0"
@@ -1636,12 +1617,8 @@ content-disposition@0.5.2:
   resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
 
 content-type-parser@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
-
-content-type@~1.0.2:
   version "1.0.2"
-  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+  resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7"
 
 content-type@~1.0.4:
   version "1.0.4"
@@ -1667,21 +1644,26 @@ core-js@^1.0.0:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
 
-core-js@^2.4.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e"
-
-core-js@^2.5.0:
+core-js@^2.4.0, core-js@^2.5.0:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
 
-core-util-is@~1.0.0:
+core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
 
+cosmiconfig@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397"
+  dependencies:
+    is-directory "^0.3.1"
+    js-yaml "^3.9.0"
+    parse-json "^3.0.0"
+    require-from-string "^2.0.1"
+
 cosmiconfig@^2.1.0, cosmiconfig@^2.1.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.3.tgz#952771eb0dddc1cb3fa2f6fbe51a522e93b3ee0a"
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892"
   dependencies:
     is-directory "^0.3.1"
     js-yaml "^3.4.3"
@@ -1698,7 +1680,7 @@ create-ecdh@^4.0.0:
     bn.js "^4.1.0"
     elliptic "^6.0.0"
 
-create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2:
+create-hash@^1.1.0, create-hash@^1.1.2:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
   dependencies:
@@ -1726,9 +1708,9 @@ create-react-class@^15.5.2:
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
-cross-env@^5.0.1:
-  version "5.0.5"
-  resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.0.5.tgz#4383d364d9660873dd185b398af3bfef5efffef3"
+cross-env@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.1.tgz#b6d8ab97f304c0f71dae7277b75fe424c08dfa74"
   dependencies:
     cross-spawn "^5.1.0"
     is-windows "^1.0.0"
@@ -1740,14 +1722,7 @@ cross-spawn@^3.0.0:
     lru-cache "^4.0.1"
     which "^1.2.9"
 
-cross-spawn@^4.0.0:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41"
-  dependencies:
-    lru-cache "^4.0.1"
-    which "^1.2.9"
-
-cross-spawn@^5.1.0:
+cross-spawn@^5.0.1, cross-spawn@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
   dependencies:
@@ -1761,6 +1736,12 @@ cryptiles@2.x.x:
   dependencies:
     boom "2.x.x"
 
+cryptiles@3.x.x:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+  dependencies:
+    boom "5.x.x"
+
 crypto-browserify@^3.11.0:
   version "3.11.1"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f"
@@ -1777,12 +1758,12 @@ crypto-browserify@^3.11.0:
     randombytes "^2.0.0"
 
 css-color-function@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.0.tgz#72c767baf978f01b8a8a94f42f17ba5d22a776fc"
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e"
   dependencies:
     balanced-match "0.1.0"
     color "^0.11.0"
-    debug "~0.7.4"
+    debug "^3.1.0"
     rgb "~0.1.0"
 
 css-color-names@0.0.4:
@@ -1952,27 +1933,17 @@ date-now@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
 
-debug@2.6.7:
-  version "2.6.7"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
-  dependencies:
-    ms "2.0.0"
-
-debug@2.6.8, debug@^2.1.1, debug@^2.2.0, debug@^2.4.5, debug@^2.6.6, debug@^2.6.8:
-  version "2.6.8"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
-  dependencies:
-    ms "2.0.0"
-
-debug@2.6.9, debug@^2.6.3:
+debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   dependencies:
     ms "2.0.0"
 
-debug@~0.7.4:
-  version "0.7.4"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39"
+debug@^3.0.1, debug@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  dependencies:
+    ms "2.0.0"
 
 decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
   version "1.2.0"
@@ -2048,10 +2019,6 @@ delegates@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
 
-depd@1.1.0, depd@~1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
-
 depd@1.1.1, depd@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
@@ -2102,8 +2069,8 @@ dns-equal@^1.0.0:
   resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
 
 dns-packet@^1.0.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.1.1.tgz#2369d45038af045f3898e6fa56862aed3f40296c"
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a"
   dependencies:
     ip "^1.1.0"
     safe-buffer "^5.0.1"
@@ -2128,7 +2095,7 @@ doctrine@^2.0.0:
     esutils "^2.0.2"
     isarray "^1.0.0"
 
-dom-helpers@^3.0.0, dom-helpers@^3.2.0, dom-helpers@^3.2.1:
+dom-helpers@^3.2.0, dom-helpers@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
 
@@ -2194,16 +2161,12 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
 
 ejs@^2.3.4, ejs@^2.5.6:
-  version "2.5.6"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88"
-
-electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.14:
-  version "1.3.15"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.15.tgz#08397934891cbcfaebbd18b82a95b5a481138369"
+  version "2.5.7"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
 
-electron-to-chromium@^1.3.18:
-  version "1.3.24"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6"
+electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.27:
+  version "1.3.27"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d"
 
 elliptic@^6.0.0:
   version "6.4.0"
@@ -2222,8 +2185,8 @@ emoji-mart@Gargron/emoji-mart#build:
   resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/a5e1afe5ebcf2841e611d20d261b029581cbe051"
 
 emoji-regex@^6.1.0:
-  version "6.4.3"
-  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.3.tgz#6ac2ac58d4b78def5e39b33fcbf395688af3076c"
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2"
 
 emojis-list@^2.0.0:
   version "2.1.0"
@@ -2252,15 +2215,16 @@ entities@^1.1.1, entities@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
 
-enzyme-adapter-react-16@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.1.tgz#066cb1735e65d8d95841a023f94dab3ce6109e17"
+enzyme-adapter-react-16@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz#8c6f431f17c69e1e9eeb25ca4bd92f31971eb2dd"
   dependencies:
     enzyme-adapter-utils "^1.0.0"
     lodash "^4.17.4"
     object.assign "^4.0.4"
     object.values "^1.0.4"
     prop-types "^15.5.10"
+    react-test-renderer "^16.0.0-0"
 
 enzyme-adapter-utils@^1.0.0:
   version "1.0.1"
@@ -2291,13 +2255,13 @@ errno@^0.1.3, errno@^0.1.4:
   dependencies:
     prr "~0.0.0"
 
-error-ex@^1.2.0:
+error-ex@^1.2.0, error-ex@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.6.1:
+es-abstract@^1.6.1, es-abstract@^1.7.0:
   version "1.9.0"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227"
   dependencies:
@@ -2307,15 +2271,6 @@ es-abstract@^1.6.1:
     is-callable "^1.1.3"
     is-regex "^1.0.4"
 
-es-abstract@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
-  dependencies:
-    es-to-primitive "^1.1.1"
-    function-bind "^1.1.0"
-    is-callable "^1.1.3"
-    is-regex "^1.0.3"
-
 es-to-primitive@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
@@ -2324,20 +2279,20 @@ es-to-primitive@^1.1.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.1"
 
-es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
-  version "0.10.24"
-  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14"
+es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
+  version "0.10.35"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.35.tgz#18ee858ce6a3c45c7d79e91c15fcca9ec568494f"
   dependencies:
-    es6-iterator "2"
-    es6-symbol "~3.1"
+    es6-iterator "~2.0.1"
+    es6-symbol "~3.1.1"
 
-es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
+es6-iterator@^2.0.1, es6-iterator@~2.0.1:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
   dependencies:
     d "1"
-    es5-ext "^0.10.14"
-    es6-symbol "^3.1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
 
 es6-map@^0.1.3:
   version "0.1.5"
@@ -2360,7 +2315,7 @@ es6-set@~0.1.5:
     es6-symbol "3.1.1"
     event-emitter "~0.3.5"
 
-es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
+es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
   dependencies:
@@ -2385,15 +2340,15 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
 escodegen@^1.6.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
   dependencies:
-    esprima "^2.7.1"
-    estraverse "^1.9.1"
+    esprima "^3.1.3"
+    estraverse "^4.2.0"
     esutils "^2.0.2"
     optionator "^0.8.1"
   optionalDependencies:
-    source-map "~0.2.0"
+    source-map "~0.5.6"
 
 escope@^3.6.0:
   version "3.6.0"
@@ -2418,9 +2373,9 @@ eslint-module-utils@^2.1.1:
     debug "^2.6.8"
     pkg-dir "^1.0.0"
 
-eslint-plugin-import@^2.7.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f"
+eslint-plugin-import@^2.8.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894"
   dependencies:
     builtin-modules "^1.1.1"
     contains-path "^0.1.0"
@@ -2495,16 +2450,20 @@ eslint@^3.19.0:
     user-home "^2.0.0"
 
 espree@^3.4.0:
-  version "3.4.3"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374"
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e"
   dependencies:
-    acorn "^5.0.1"
+    acorn "^5.1.1"
     acorn-jsx "^3.0.0"
 
-esprima@^2.6.0, esprima@^2.7.1:
+esprima@^2.6.0:
   version "2.7.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
 
+esprima@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
 esprima@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
@@ -2522,22 +2481,14 @@ esrecurse@^4.1.0:
     estraverse "^4.1.0"
     object-assign "^4.0.1"
 
-estraverse@^1.9.1:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
-
 estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
 
-esutils@^2.0.0, esutils@^2.0.2:
+esutils@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
 
-etag@~1.8.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
-
 etag@~1.8.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
@@ -2563,11 +2514,12 @@ eventsource@0.1.6:
   dependencies:
     original ">=0.0.5"
 
-evp_bytestokey@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53"
+evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
   dependencies:
-    create-hash "^1.1.1"
+    md5.js "^1.3.4"
+    safe-buffer "^5.1.1"
 
 exec-sh@^0.2.0:
   version "0.2.1"
@@ -2575,12 +2527,12 @@ exec-sh@^0.2.0:
   dependencies:
     merge "^1.1.3"
 
-execa@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36"
+execa@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
   dependencies:
-    cross-spawn "^4.0.0"
-    get-stream "^2.2.0"
+    cross-spawn "^5.0.1"
+    get-stream "^3.0.0"
     is-stream "^1.1.0"
     npm-run-path "^2.0.0"
     p-finally "^1.0.0"
@@ -2614,42 +2566,9 @@ expect@^21.2.1:
     jest-message-util "^21.2.1"
     jest-regex-util "^21.2.0"
 
-express@^4.13.3:
-  version "4.15.3"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
-  dependencies:
-    accepts "~1.3.3"
-    array-flatten "1.1.1"
-    content-disposition "0.5.2"
-    content-type "~1.0.2"
-    cookie "0.3.1"
-    cookie-signature "1.0.6"
-    debug "2.6.7"
-    depd "~1.1.0"
-    encodeurl "~1.0.1"
-    escape-html "~1.0.3"
-    etag "~1.8.0"
-    finalhandler "~1.0.3"
-    fresh "0.5.0"
-    merge-descriptors "1.0.1"
-    methods "~1.1.2"
-    on-finished "~2.3.0"
-    parseurl "~1.3.1"
-    path-to-regexp "0.1.7"
-    proxy-addr "~1.1.4"
-    qs "6.4.0"
-    range-parser "~1.2.0"
-    send "0.15.3"
-    serve-static "1.12.3"
-    setprototypeof "1.0.3"
-    statuses "~1.3.1"
-    type-is "~1.6.15"
-    utils-merge "1.0.0"
-    vary "~1.1.1"
-
-express@^4.15.2:
-  version "4.16.1"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0"
+express@^4.13.3, express@^4.15.2, express@^4.16.2:
+  version "4.16.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
   dependencies:
     accepts "~1.3.4"
     array-flatten "1.1.1"
@@ -2682,7 +2601,7 @@ express@^4.15.2:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
-extend@~3.0.0:
+extend@~3.0.0, extend@~3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
 
@@ -2692,23 +2611,27 @@ extglob@^0.3.1:
   dependencies:
     is-extglob "^1.0.0"
 
-extract-text-webpack-plugin@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.2.tgz#756ef4efa8155c3681833fbc34da53b941746d6c"
+extract-text-webpack-plugin@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz#5f043eaa02f9750a9258b78c0a6e0dc1408fb2f7"
   dependencies:
-    async "^2.1.2"
-    loader-utils "^1.0.2"
+    async "^2.4.1"
+    loader-utils "^1.1.0"
     schema-utils "^0.3.0"
     webpack-sources "^1.0.1"
 
-extsprintf@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+extsprintf@1.3.0, extsprintf@^1.2.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
 
 fast-deep-equal@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
 
+fast-json-stable-stringify@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
 fast-levenshtein@~2.0.4:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -2735,7 +2658,7 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
-fbjs@^0.8.14, fbjs@^0.8.16:
+fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.9:
   version "0.8.16"
   resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
   dependencies:
@@ -2747,18 +2670,6 @@ fbjs@^0.8.14, fbjs@^0.8.16:
     setimmediate "^1.0.5"
     ua-parser-js "^0.7.9"
 
-fbjs@^0.8.4, fbjs@^0.8.9:
-  version "0.8.12"
-  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04"
-  dependencies:
-    core-js "^1.0.0"
-    isomorphic-fetch "^2.1.1"
-    loose-envify "^1.0.0"
-    object-assign "^4.1.0"
-    promise "^7.1.1"
-    setimmediate "^1.0.5"
-    ua-parser-js "^0.7.9"
-
 figures@^1.3.5:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -2791,8 +2702,8 @@ fileset@^2.0.2:
     minimatch "^3.0.3"
 
 filesize@^3.5.9:
-  version "3.5.10"
-  resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f"
+  version "3.5.11"
+  resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee"
 
 fill-range@^2.1.0:
   version "2.2.3"
@@ -2816,18 +2727,6 @@ finalhandler@1.1.0:
     statuses "~1.3.1"
     unpipe "~1.0.0"
 
-finalhandler@~1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
-  dependencies:
-    debug "2.6.7"
-    encodeurl "~1.0.1"
-    escape-html "~1.0.3"
-    on-finished "~2.3.0"
-    parseurl "~1.3.1"
-    statuses "~1.3.1"
-    unpipe "~1.0.0"
-
 find-cache-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
@@ -2850,8 +2749,8 @@ find-up@^2.0.0, find-up@^2.1.0:
     locate-path "^2.0.0"
 
 flat-cache@^1.2.1:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
   dependencies:
     circular-json "^0.3.1"
     del "^2.0.2"
@@ -2863,10 +2762,10 @@ flatten@^1.0.2:
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
 
 follow-redirects@^1.2.3:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.4.tgz#355e8f4d16876b43f577b0d5ce2668b9723214ea"
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.5.tgz#ffd3e14cbdd5eaa72f61b6368c1f68516c2a26cc"
   dependencies:
-    debug "^2.4.5"
+    debug "^2.6.9"
 
 font-awesome@^4.7.0:
   version "4.7.0"
@@ -2908,9 +2807,13 @@ form-data@~2.1.1:
     combined-stream "^1.0.5"
     mime-types "^2.1.12"
 
-forwarded@~0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
+form-data@~2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.5"
+    mime-types "^2.1.12"
 
 forwarded@~0.1.2:
   version "0.1.2"
@@ -2920,10 +2823,6 @@ fraction.js@4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.2.tgz#0eae896626f334b1bde763371347a83b5575d7f0"
 
-fresh@0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
-
 fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@@ -3019,12 +2918,9 @@ get-stdin@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
 
-get-stream@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
-  dependencies:
-    object-assign "^4.0.1"
-    pinkie-promise "^2.0.0"
+get-stream@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
 
 getpass@^0.1.1:
   version "0.1.7"
@@ -3056,7 +2952,11 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-globals@^9.0.0, globals@^9.14.0, globals@^9.18.0:
+globals@^10.0.0:
+  version "10.2.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-10.2.0.tgz#69490789091fcaa7f7d512c668c8eb73894a4ef2"
+
+globals@^9.14.0, globals@^9.18.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
 
@@ -3090,8 +2990,8 @@ globule@^1.0.0:
     minimatch "~3.0.2"
 
 gonzales-pe@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.0.3.tgz#36148e18e267184fbfdc929af28f29ad9fbf9746"
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.2.tgz#f50a8c17842f13a9007909b7cb32188266e4d74c"
   dependencies:
     minimist "1.1.x"
 
@@ -3114,8 +3014,8 @@ handle-thing@^1.2.5:
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
 
 handlebars@^4.0.3:
-  version "4.0.10"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f"
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
   dependencies:
     async "^1.4.0"
     optimist "^0.6.1"
@@ -3127,6 +3027,10 @@ har-schema@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
 
+har-schema@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
 har-validator@~4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
@@ -3134,6 +3038,13 @@ har-validator@~4.2.1:
     ajv "^4.9.1"
     har-schema "^1.0.5"
 
+har-validator@~5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+  dependencies:
+    ajv "^5.1.0"
+    har-schema "^2.0.0"
+
 has-ansi@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -3164,6 +3075,13 @@ hash-base@^2.0.0:
   dependencies:
     inherits "^2.0.1"
 
+hash-base@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
+  dependencies:
+    inherits "^2.0.1"
+    safe-buffer "^5.0.1"
+
 hash.js@^1.0.0, hash.js@^1.0.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
@@ -3171,7 +3089,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.0"
 
-hawk@~3.1.3:
+hawk@3.1.3, hawk@~3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
   dependencies:
@@ -3180,6 +3098,15 @@ hawk@~3.1.3:
     hoek "2.x.x"
     sntp "1.x.x"
 
+hawk@~6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+  dependencies:
+    boom "4.x.x"
+    cryptiles "3.x.x"
+    hoek "4.x.x"
+    sntp "2.x.x"
+
 history@^4.7.2:
   version "4.7.2"
   resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b"
@@ -3202,6 +3129,10 @@ hoek@2.x.x:
   version "2.16.3"
   resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
 
+hoek@4.x.x:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
+
 hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@@ -3231,8 +3162,8 @@ html-comment-regex@^1.1.0:
   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e"
 
 html-encoding-sniffer@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
   dependencies:
     whatwg-encoding "^1.0.1"
 
@@ -3264,19 +3195,14 @@ http-errors@1.6.2, http-errors@~1.6.2:
     setprototypeof "1.0.3"
     statuses ">= 1.3.1 < 2"
 
-http-errors@~1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257"
-  dependencies:
-    depd "1.1.0"
-    inherits "2.0.3"
-    setprototypeof "1.0.3"
-    statuses ">= 1.3.1 < 2"
-
 http-link-header@^0.8.0:
   version "0.8.0"
   resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-0.8.0.tgz#a22b41a0c9b1e2d8fac1bf1b697c6bd532d5f5e4"
 
+http-parser-js@>=0.4.0:
+  version "0.4.9"
+  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1"
+
 http-proxy-middleware@~0.17.4:
   version "0.17.4"
   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
@@ -3301,22 +3227,22 @@ http-signature@~1.1.0:
     jsprim "^1.2.2"
     sshpk "^1.7.0"
 
+http-signature@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
 https-browserify@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
 
-iconv-lite@0.4.13:
-  version "0.4.13"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
-
-iconv-lite@0.4.19:
+iconv-lite@0.4.19, iconv-lite@~0.4.13:
   version "0.4.19"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
 
-iconv-lite@~0.4.13:
-  version "0.4.18"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
-
 icss-replace-symbols@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -3332,12 +3258,19 @@ ieee754@^1.1.4:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
 
 ignore@^3.2.0:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
+  version "3.3.7"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021"
 
-immutable@^3.8.1:
-  version "3.8.1"
-  resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
+immutable@^3.8.2:
+  version "3.8.2"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
+
+import-local@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8"
+  dependencies:
+    pkg-dir "^2.0.0"
+    resolve-cwd "^2.0.0"
 
 imurmurhash@^0.1.4:
   version "0.1.4"
@@ -3405,8 +3338,8 @@ internal-ip@1.2.0:
     meow "^3.3.0"
 
 interpret@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
 
 intersection-observer@^0.4.0:
   version "0.4.2"
@@ -3421,24 +3354,18 @@ intl-messageformat-parser@1.2.0:
   resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz#5906b7f953ab7470e0dc8549097b648b991892ff"
 
 intl-messageformat-parser@^1.2.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.3.0.tgz#c5d26ffb894c7d9c2b9fa444c67f417ab2594268"
-
-intl-messageformat@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.0.0.tgz#3d56982583425aee23b76c8b985fb9b0aae5be3c"
-  dependencies:
-    intl-messageformat-parser "1.2.0"
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075"
 
-intl-messageformat@^2.1.0:
+intl-messageformat@^2.0.0, intl-messageformat@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.1.0.tgz#1c51da76f02a3f7b360654cdc51bbc4d3fa6c72c"
   dependencies:
     intl-messageformat-parser "1.2.0"
 
-intl-relativeformat@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.0.0.tgz#d6ba9dc6c625819bc0abdb1d4e238138b7488f26"
+intl-relativeformat@^2.0.0, intl-relativeformat@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.1.0.tgz#010f1105802251f40ac47d0e3e1a201348a255df"
   dependencies:
     intl-messageformat "^2.0.0"
 
@@ -3460,10 +3387,6 @@ ip@^1.1.0, ip@^1.1.5:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
 
-ipaddr.js@1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
-
 ipaddr.js@1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
@@ -3483,8 +3406,8 @@ is-binary-path@^1.0.0:
     binary-extensions "^1.0.0"
 
 is-buffer@^1.0.2, is-buffer@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
 
 is-builtin-module@^1.0.0:
   version "1.0.0"
@@ -3561,8 +3484,8 @@ is-glob@^3.1.0:
     is-extglob "^2.1.0"
 
 is-my-json-valid@^2.10.0:
-  version "2.16.0"
-  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693"
+  version "2.16.1"
+  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11"
   dependencies:
     generate-function "^2.0.0"
     generate-object-property "^1.1.0"
@@ -3625,7 +3548,7 @@ is-property@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
 
-is-regex@^1.0.3, is-regex@^1.0.4:
+is-regex@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
   dependencies:
@@ -3705,17 +3628,17 @@ isstream@~0.1.2:
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
 
 istanbul-api@^1.1.1:
-  version "1.1.14"
-  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680"
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620"
   dependencies:
     async "^2.1.4"
     fileset "^2.0.2"
     istanbul-lib-coverage "^1.1.1"
-    istanbul-lib-hook "^1.0.7"
-    istanbul-lib-instrument "^1.8.0"
-    istanbul-lib-report "^1.1.1"
-    istanbul-lib-source-maps "^1.2.1"
-    istanbul-reports "^1.1.2"
+    istanbul-lib-hook "^1.1.0"
+    istanbul-lib-instrument "^1.9.1"
+    istanbul-lib-report "^1.1.2"
+    istanbul-lib-source-maps "^1.2.2"
+    istanbul-reports "^1.1.3"
     js-yaml "^3.7.0"
     mkdirp "^0.5.1"
     once "^1.4.0"
@@ -3724,15 +3647,15 @@ istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
 
-istanbul-lib-hook@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc"
+istanbul-lib-hook@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b"
   dependencies:
     append-transform "^0.4.0"
 
-istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532"
+istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e"
   dependencies:
     babel-generator "^6.18.0"
     babel-template "^6.16.0"
@@ -3742,28 +3665,28 @@ istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-ins
     istanbul-lib-coverage "^1.1.1"
     semver "^5.3.0"
 
-istanbul-lib-report@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9"
+istanbul-lib-report@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425"
   dependencies:
     istanbul-lib-coverage "^1.1.1"
     mkdirp "^0.5.1"
     path-parse "^1.0.5"
     supports-color "^3.1.2"
 
-istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c"
+istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c"
   dependencies:
-    debug "^2.6.3"
+    debug "^3.1.0"
     istanbul-lib-coverage "^1.1.1"
     mkdirp "^0.5.1"
     rimraf "^2.6.1"
     source-map "^0.5.3"
 
-istanbul-reports@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f"
+istanbul-reports@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10"
   dependencies:
     handlebars "^4.0.3"
 
@@ -3997,8 +3920,8 @@ jest@^21.2.1:
     jest-cli "^21.2.1"
 
 js-base64@^2.1.8, js-base64@^2.1.9:
-  version "2.1.9"
-  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce"
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf"
 
 js-string-escape@1.0.1:
   version "1.0.1"
@@ -4008,14 +3931,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
-js-yaml@^3.4.3, js-yaml@^3.5.1:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce"
-  dependencies:
-    argparse "^1.0.7"
-    esprima "^4.0.0"
-
-js-yaml@^3.7.0, js-yaml@^3.9.0:
+js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.9.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
   dependencies:
@@ -4066,8 +3982,8 @@ jsesc@~0.5.0:
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
 
 json-loader@^0.5.4:
-  version "0.5.4"
-  resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
+  version "0.5.7"
+  resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
 
 json-schema-traverse@^0.3.0:
   version "0.3.1"
@@ -4110,13 +4026,13 @@ jsonpointer@^4.0.0:
   resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
 
 jsprim@^1.2.2:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
   dependencies:
     assert-plus "1.0.0"
-    extsprintf "1.0.2"
+    extsprintf "1.3.0"
     json-schema "0.2.3"
-    verror "1.3.6"
+    verror "1.10.0"
 
 jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4:
   version "1.4.1"
@@ -4332,13 +4248,13 @@ lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
 
-"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4:
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.4:
   version "4.17.4"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
 
 loglevel@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd"
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.5.1.tgz#189078c94ab9053ee215a0acdbf24244ea0f6502"
 
 longest@^1.0.1:
   version "1.0.1"
@@ -4369,10 +4285,10 @@ macaddress@^0.2.8:
   resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
 
 make-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51"
   dependencies:
-    pify "^2.3.0"
+    pify "^3.0.0"
 
 makeerror@1.0.x:
   version "1.0.11"
@@ -4397,8 +4313,8 @@ math-expression-evaluator@^1.2.14:
   resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
 
 mathjs@^3.11.5:
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.14.2.tgz#bb79b7dc878b7f586ce408ab067a9a42db2e7a2d"
+  version "3.16.5"
+  resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-3.16.5.tgz#d75a5265435d2824b067b37a478771deebf6aacc"
   dependencies:
     complex.js "2.0.4"
     decimal.js "7.2.3"
@@ -4408,6 +4324,13 @@ mathjs@^3.11.5:
     tiny-emitter "2.0.0"
     typed-function "0.10.5"
 
+md5.js@^1.3.4:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d"
+  dependencies:
+    hash-base "^3.0.0"
+    inherits "^2.0.1"
+
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -4471,48 +4394,30 @@ micromatch@^2.1.5, micromatch@^2.3.11:
     regex-cache "^0.4.2"
 
 miller-rabin@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d"
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
   dependencies:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-"mime-db@>= 1.27.0 < 2":
-  version "1.29.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878"
-
-mime-db@~1.27.0:
-  version "1.27.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
+"mime-db@>= 1.30.0 < 2":
+  version "1.31.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb"
 
 mime-db@~1.30.0:
   version "1.30.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
 
-mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
-  version "2.1.15"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
-  dependencies:
-    mime-db "~1.27.0"
-
-mime-types@~2.1.16:
+mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7:
   version "2.1.17"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
   dependencies:
     mime-db "~1.30.0"
 
-mime@1.3.4:
-  version "1.3.4"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
-
-mime@1.4.1:
+mime@1.4.1, mime@^1.3.4:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
 
-mime@^1.3.4:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0"
-
 mimic-fn@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
@@ -4584,8 +4489,8 @@ mute-stream@0.0.5:
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
 
 nan@^2.0.0, nan@^2.3.0, nan@^2.3.2:
-  version "2.6.2"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
 
 natural-compare@^1.4.0:
   version "1.4.0"
@@ -4604,8 +4509,8 @@ negotiator@0.6.1:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
 
 node-fetch@^1.0.1:
-  version "1.7.1"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5"
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
   dependencies:
     encoding "^0.1.11"
     is-stream "^1.0.1"
@@ -4674,14 +4579,15 @@ node-notifier@^5.0.2:
     which "^1.2.12"
 
 node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.4:
-  version "0.6.36"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
+  version "0.6.38"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d"
   dependencies:
+    hawk "3.1.3"
     mkdirp "^0.5.1"
     nopt "^4.0.1"
     npmlog "^4.0.2"
     rc "^1.1.7"
-    request "^2.81.0"
+    request "2.81.0"
     rimraf "^2.6.1"
     semver "^5.3.0"
     tar "^2.2.1"
@@ -4710,7 +4616,7 @@ node-sass@^4.5.2:
     sass-graph "^2.1.1"
     stdout-stream "^1.4.0"
 
-node-zopfli@^2.0.0:
+node-zopfli@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/node-zopfli/-/node-zopfli-2.0.2.tgz#a7a473ae92aaea85d4c68d45bbf2c944c46116b8"
   dependencies:
@@ -4748,7 +4654,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
     semver "2 || 3 || 4 || 5"
     validate-npm-package-license "^3.0.1"
 
-normalize-path@^2.0.1:
+normalize-path@^2.0.0, normalize-path@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
   dependencies:
@@ -4800,7 +4706,7 @@ number-is-nan@^1.0.0:
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"
 
-oauth-sign@~0.8.1:
+oauth-sign@~0.8.1, oauth-sign@~0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
 
@@ -4944,10 +4850,10 @@ os-locale@^1.4.0:
     lcid "^1.0.0"
 
 os-locale@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.0.0.tgz#15918ded510522b81ee7ae5a309d54f639fc39a4"
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
   dependencies:
-    execa "^0.5.0"
+    execa "^0.7.0"
     lcid "^1.0.0"
     mem "^1.1.0"
 
@@ -4981,8 +4887,8 @@ p-locate@^2.0.0:
     p-limit "^1.1.0"
 
 p-map@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
 
 packet-reader@0.3.1:
   version "0.3.1"
@@ -5031,6 +4937,12 @@ parse-json@^2.2.0:
   dependencies:
     error-ex "^1.2.0"
 
+parse-json@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13"
+  dependencies:
+    error-ex "^1.3.1"
+
 parse5@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
@@ -5041,10 +4953,6 @@ parse5@^3.0.1:
   dependencies:
     "@types/node" "^6.0.46"
 
-parseurl@~1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
-
 parseurl@~1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
@@ -5108,8 +5016,8 @@ path-type@^2.0.0:
     pify "^2.0.0"
 
 pbkdf2@^3.0.3:
-  version "3.0.12"
-  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2"
+  version "3.0.14"
+  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
   dependencies:
     create-hash "^1.1.2"
     create-hmac "^1.1.4"
@@ -5137,10 +5045,9 @@ pg-pool@1.*:
     object-assign "4.1.0"
 
 pg-types@1.*:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.0.tgz#8ad3b7b897e3fd463e62de241ad5fc640b4a66f0"
+  version "1.12.1"
+  resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.1.tgz#d64087e3903b58ffaad279e7595c52208a14c3d2"
   dependencies:
-    ap "~0.2.0"
     postgres-array "~1.0.0"
     postgres-bytea "~1.0.0"
     postgres-date "~1.0.0"
@@ -5258,11 +5165,11 @@ postcss-custom-media@^6.0.0:
     postcss "^6.0.1"
 
 postcss-custom-properties@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.1.0.tgz#9caf1151ac41b1e9e64d3a2ff9ece996ca18977d"
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.2.0.tgz#5d929a7f06e9b84e0f11334194c0ba9a30acfbe9"
   dependencies:
     balanced-match "^1.0.0"
-    postcss "^6.0.3"
+    postcss "^6.0.13"
 
 postcss-custom-selectors@^4.0.1:
   version "4.0.1"
@@ -5325,12 +5232,12 @@ postcss-import@^10.0.0:
     read-cache "^1.0.0"
     resolve "^1.1.7"
 
-postcss-js@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-1.0.0.tgz#ccee5aa3b1970dd457008e79438165f66919ba30"
+postcss-js@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-1.0.1.tgz#ffaf29226e399ea74b5dce02cab1729d7addbc7b"
   dependencies:
     camelcase-css "^1.0.1"
-    postcss "^6.0.1"
+    postcss "^6.0.11"
 
 postcss-load-config@^1.2.0:
   version "1.2.0"
@@ -5355,12 +5262,12 @@ postcss-load-plugins@^2.3.0:
     cosmiconfig "^2.1.1"
     object-assign "^4.1.0"
 
-postcss-loader@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc"
+postcss-loader@^2.0.8:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.8.tgz#8c67ddb029407dfafe684a406cfc16bad2ce0814"
   dependencies:
     loader-utils "^1.1.0"
-    postcss "^6.0.2"
+    postcss "^6.0.0"
     postcss-load-config "^1.2.0"
     schema-utils "^0.3.0"
 
@@ -5432,13 +5339,13 @@ postcss-minify-selectors@^2.0.4:
     postcss-selector-parser "^2.0.0"
 
 postcss-mixins@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-6.0.1.tgz#f5c9726259a6103733b43daa6a8b67dd0ed7aa47"
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-6.2.0.tgz#fa9d2c2166b2ae7745956c727ab9dd2de4b96a40"
   dependencies:
     globby "^6.1.0"
-    postcss "^6.0.3"
-    postcss-js "^1.0.0"
-    postcss-simple-vars "^4.0.0"
+    postcss "^6.0.13"
+    postcss-js "^1.0.1"
+    postcss-simple-vars "^4.1.0"
     sugarss "^1.0.0"
 
 postcss-modules-extract-imports@^1.0.0:
@@ -5469,16 +5376,17 @@ postcss-modules-values@^1.1.0:
     postcss "^6.0.1"
 
 postcss-nested@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-2.0.2.tgz#f38fad547f5c3747160aec3bb34745819252974a"
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-2.1.2.tgz#04057281f9631fef684857fb0119bae04ede03c6"
   dependencies:
-    postcss "^6.0.1"
+    postcss "^6.0.9"
+    postcss-selector-parser "^2.2.3"
 
 postcss-nesting@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.0.1.tgz#8fc2ce40cbfcfab7ee24e7b68fb6ebe84b641469"
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d"
   dependencies:
-    postcss "^6.0.1"
+    postcss "^6.0.11"
 
 postcss-normalize-charset@^1.1.0:
   version "1.1.1"
@@ -5574,7 +5482,7 @@ postcss-selector-not@^3.0.1:
     balanced-match "^0.4.2"
     postcss "^6.0.1"
 
-postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
+postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90"
   dependencies:
@@ -5582,11 +5490,11 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2:
     indexes-of "^1.0.1"
     uniq "^1.0.1"
 
-postcss-simple-vars@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-4.0.0.tgz#d49e082897d9a4824f2268fa91d969d943e2ea76"
+postcss-simple-vars@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-4.1.0.tgz#043248cfef8d3f51b3486a28c09f8375dbf1b2f9"
   dependencies:
-    postcss "^6.0.1"
+    postcss "^6.0.9"
 
 postcss-smart-import@^0.7.5:
   version "0.7.5"
@@ -5634,28 +5542,20 @@ postcss-zindex@^2.0.1:
     uniqs "^2.0.0"
 
 postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16, postcss@^5.2.6:
-  version "5.2.17"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b"
+  version "5.2.18"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
   dependencies:
     chalk "^1.1.3"
     js-base64 "^2.1.9"
     source-map "^0.5.6"
     supports-color "^3.2.3"
 
-postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.6:
-  version "6.0.6"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.6.tgz#bba4d58e884fc78c840d1539e10eddaabb8f73bd"
-  dependencies:
-    chalk "^2.0.1"
-    source-map "^0.5.6"
-    supports-color "^4.1.0"
-
-postcss@^6.0.11:
-  version "6.0.12"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.12.tgz#6b0155089d2d212f7bd6a0cecd4c58c007403535"
+postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.3, postcss@^6.0.6, postcss@^6.0.9:
+  version "6.0.13"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f"
   dependencies:
     chalk "^2.1.0"
-    source-map "^0.5.7"
+    source-map "^0.6.1"
     supports-color "^4.4.0"
 
 postgres-array@~1.0.0:
@@ -5671,8 +5571,8 @@ postgres-date@~1.0.0:
   resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8"
 
 postgres-interval@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.0.tgz#1031e7bac34564132862adc9eb6c6d2f3aa75bb4"
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.1.tgz#acdb0f897b4b1c6e496d9d4e0a853e1c428f06f0"
   dependencies:
     xtend "^4.0.0"
 
@@ -5721,8 +5621,8 @@ pretty-format@^21.2.1:
     ansi-styles "^3.2.0"
 
 private@^0.1.6, private@^0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
 
 process-nextick-args@~1.0.6:
   version "1.0.7"
@@ -5754,7 +5654,7 @@ prop-types-extra@^1.0.1:
   dependencies:
     warning "^3.0.0"
 
-prop-types@^15.5.10, prop-types@^15.6.0:
+prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0:
   version "15.6.0"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
   dependencies:
@@ -5762,20 +5662,6 @@ prop-types@^15.5.10, prop-types@^15.6.0:
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
-prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.8:
-  version "15.5.10"
-  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
-  dependencies:
-    fbjs "^0.8.9"
-    loose-envify "^1.3.1"
-
-proxy-addr@~1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3"
-  dependencies:
-    forwarded "~0.1.0"
-    ipaddr.js "1.3.0"
-
 proxy-addr@~2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
@@ -5814,17 +5700,17 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
 
 q@^1.1.2:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1"
-
-qs@6.4.0, qs@~6.4.0:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
 
-qs@6.5.1:
+qs@6.5.1, qs@~6.5.1:
   version "6.5.1"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
 
+qs@~6.4.0:
+  version "6.4.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
 query-string@^4.1.0:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
@@ -5852,13 +5738,7 @@ quote@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01"
 
-raf@^3.1.0:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/raf/-/raf-3.3.2.tgz#0c13be0b5b49b46f76d6669248d527cf2b02fe27"
-  dependencies:
-    performance-now "^2.1.0"
-
-raf@^3.3.2, raf@^3.4.0:
+raf@^3.1.0, raf@^3.3.2, raf@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575"
   dependencies:
@@ -5906,8 +5786,8 @@ raw-body@2.3.2:
     unpipe "1.0.0"
 
 rc@^1.1.7:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
   dependencies:
     deep-extend "~0.4.0"
     ini "~1.3.0"
@@ -5924,12 +5804,12 @@ react-dom@^16.0.0:
     prop-types "^15.6.0"
 
 react-event-listener@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.0.tgz#d82105135573e187e3d900d18150a5882304b8d1"
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/react-event-listener/-/react-event-listener-0.5.1.tgz#ba36076e47bc37c5a67ff5ccd4a9ff0f15621040"
   dependencies:
     babel-runtime "^6.26.0"
-    fbjs "^0.8.14"
-    prop-types "^15.5.10"
+    fbjs "^0.8.16"
+    prop-types "^15.6.0"
     warning "^3.0.0"
 
 react-hotkeys@^0.10.0:
@@ -5945,9 +5825,9 @@ react-immutable-proptypes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
 
-react-immutable-pure-component@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.0.1.tgz#c36b11546822a17fbc115c43278fc1698147687f"
+react-immutable-pure-component@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.1.1.tgz#0ff69c6d4eaecd886db16805f3bbc1d2a2c654d8"
 
 react-intl-translations-manager@^5.0.0:
   version "5.0.1"
@@ -5967,34 +5847,34 @@ react-intl@^2.4.0:
     intl-relativeformat "^2.0.0"
     invariant "^2.1.1"
 
-react-motion@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.1.tgz#b90631408175ab1668e173caccd66d41a44f4592"
+react-motion@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
   dependencies:
     performance-now "^0.2.0"
     prop-types "^15.5.8"
     raf "^3.1.0"
 
-react-notification@^6.7.1:
-  version "6.8.0"
-  resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.8.0.tgz#9cb7aa06c8e5085b4c0dc2e8d9aa1da1fbc61d93"
+react-notification@^6.8.2:
+  version "6.8.2"
+  resolved "https://registry.yarnpkg.com/react-notification/-/react-notification-6.8.2.tgz#5d9910cc2993bd51a234d3809e94a98dc490cfb0"
   dependencies:
     prop-types "^15.5.10"
 
-react-overlays@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.1.tgz#26e480003c2fd6f581a4a66c0c86cb3dff17e626"
+react-overlays@^0.8.3:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.8.3.tgz#fad65eea5b24301cca192a169f5dddb0b20d3ac5"
   dependencies:
     classnames "^2.2.5"
     dom-helpers "^3.2.1"
     prop-types "^15.5.10"
     prop-types-extra "^1.0.1"
-    react-transition-group "^2.0.0-beta.0"
+    react-transition-group "^2.2.0"
     warning "^3.0.0"
 
-react-redux-loading-bar@^2.9.2:
-  version "2.9.2"
-  resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.9.2.tgz#f0e604ee35af5ecb25addb10bf24ca3d478c95a8"
+react-redux-loading-bar@^2.9.3:
+  version "2.9.3"
+  resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.9.3.tgz#65865dddcbf597169e787edec15eec7ebfb84149"
   dependencies:
     prop-types "^15.5.6"
 
@@ -6020,11 +5900,11 @@ react-router-dom@^4.1.1:
     react-router "^4.2.0"
     warning "^3.0.0"
 
-react-router-scroll@Gargron/react-router-scroll#build:
-  version "0.4.3"
-  resolved "https://codeload.github.com/Gargron/react-router-scroll/tar.gz/17a028e3c2db0e488c6dca6ab1639783fb54480a"
+react-router-scroll-4@^1.0.0-beta.1:
+  version "1.0.0-beta.1"
+  resolved "https://registry.yarnpkg.com/react-router-scroll-4/-/react-router-scroll-4-1.0.0-beta.1.tgz#b1838c6e67b9fe64db33176958e88836e10bc4ce"
   dependencies:
-    prop-types "^15.6.0"
+    babel-eslint "^8.0.1"
     scroll-behavior "^0.9.1"
     warning "^3.0.0"
 
@@ -6069,7 +5949,7 @@ react-swipeable-views@^0.12.3:
     react-swipeable-views-utils "^0.12.8"
     warning "^3.0.0"
 
-react-test-renderer@^16.0.0:
+react-test-renderer@^16.0.0, react-test-renderer@^16.0.0-0:
   version "16.0.0"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15"
   dependencies:
@@ -6088,9 +5968,9 @@ react-toggle@^4.0.1:
   dependencies:
     classnames "^2.2.5"
 
-react-transition-group@^2.0.0-beta.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.0.tgz#793bf8cb15bfe91b3101b24bce1c1d2891659575"
+react-transition-group@^2.2.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10"
   dependencies:
     chain-function "^1.0.0"
     classnames "^2.2.5"
@@ -6234,35 +6114,30 @@ redux@^3.7.1:
     symbol-observable "^1.0.3"
 
 regenerate@^1.2.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
-
-regenerator-runtime@^0.10.0:
-  version "0.10.5"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
 
 regenerator-runtime@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
 
-regenerator-transform@0.9.11:
-  version "0.9.11"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283"
+regenerator-transform@^0.10.0:
+  version "0.10.1"
+  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
   dependencies:
     babel-runtime "^6.18.0"
     babel-types "^6.19.0"
     private "^0.1.6"
 
 regex-cache@^0.4.2:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
   dependencies:
     is-equal-shallow "^0.1.3"
-    is-primitive "^2.0.0"
 
 regex-parser@^2.2.1:
-  version "2.2.7"
-  resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.7.tgz#bd090e09181849acc45457e765f7be2a63f50ef1"
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.8.tgz#da4c0cda5a828559094168930f455f532b6ffbac"
 
 regexpu-core@^1.0.0:
   version "1.0.0"
@@ -6291,8 +6166,8 @@ regjsparser@^0.1.4:
     jsesc "~0.5.0"
 
 remove-trailing-separator@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511"
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
 
 repeat-element@^1.1.2:
   version "1.1.2"
@@ -6308,7 +6183,34 @@ repeating@^2.0.0:
   dependencies:
     is-finite "^1.0.0"
 
-request@2, request@^2.79.0, request@^2.81.0:
+request@2, request@^2.79.0:
+  version "2.83.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.6.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.5"
+    extend "~3.0.1"
+    forever-agent "~0.6.1"
+    form-data "~2.3.1"
+    har-validator "~5.0.3"
+    hawk "~6.0.2"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.17"
+    oauth-sign "~0.8.2"
+    performance-now "^2.1.0"
+    qs "~6.5.1"
+    safe-buffer "^5.1.1"
+    stringstream "~0.0.5"
+    tough-cookie "~2.3.3"
+    tunnel-agent "^0.6.0"
+    uuid "^3.1.0"
+
+request@2.81.0:
   version "2.81.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
   dependencies:
@@ -6343,10 +6245,14 @@ require-directory@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
 
-require-from-string@^1.1.0, require-from-string@^1.2.1:
+require-from-string@^1.1.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418"
 
+require-from-string@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.1.tgz#c545233e9d7da6616e9d59adfb39fc9f588676ff"
+
 require-main-filename@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@@ -6366,17 +6272,27 @@ reselect@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
 
+resolve-cwd@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
+  dependencies:
+    resolve-from "^3.0.0"
+
 resolve-from@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
 
+resolve-from@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
+
 resolve-pathname@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
 
-resolve-url-loader@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.0.tgz#27c95cc16a4353923fdbdc2dbaf5eef22232c477"
+resolve-url-loader@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.2.0.tgz#9662feaa11debf7cf8e3feb91dae9544aa7dee88"
   dependencies:
     adjust-sourcemap-loader "^1.1.0"
     camelcase "^4.0.0"
@@ -6396,15 +6312,9 @@ resolve@1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
-resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
-  dependencies:
-    path-parse "^1.0.5"
-
-resolve@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
+resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.3:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
   dependencies:
     path-parse "^1.0.5"
 
@@ -6440,13 +6350,7 @@ right-align@^0.1.1:
   dependencies:
     align-text "^0.1.1"
 
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
-  dependencies:
-    glob "^7.0.5"
-
-rimraf@^2.6.1:
+rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1:
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
   dependencies:
@@ -6460,8 +6364,8 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
     inherits "^2.0.1"
 
 rst-selector-parser@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.2.tgz#9927b619bd5af8dc23a76c64caef04edf90d2c65"
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91"
   dependencies:
     lodash.flattendeep "^4.4.0"
     nearley "^2.7.10"
@@ -6476,7 +6380,7 @@ rx-lite@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
 
-safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
 
@@ -6528,11 +6432,11 @@ schema-utils@^0.3.0:
     ajv "^5.0.0"
 
 scroll-behavior@^0.9.1:
-  version "0.9.3"
-  resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.3.tgz#e48bcc8af364f3f07176e8dbca3968bd5e71557b"
+  version "0.9.4"
+  resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.4.tgz#73b4a0eae3e59c0b8f3b6fc1ff78f054a513e79c"
   dependencies:
-    dom-helpers "^3.0.0"
-    invariant "^2.2.1"
+    dom-helpers "^3.2.1"
+    invariant "^2.2.2"
 
 scss-tokenizer@^0.2.3:
   version "0.2.3"
@@ -6550,36 +6454,22 @@ select-hose@^2.0.0:
   resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
 
 selfsigned@^1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.9.1.tgz#cdda4492d70d486570f87c65546023558e1dfa5a"
+  version "1.10.1"
+  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52"
   dependencies:
     node-forge "0.6.33"
 
-"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@~5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+"semver@2 || 3 || 4 || 5", semver@^5.3.0:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
 
 semver@4.3.2:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
 
-send@0.15.3:
-  version "0.15.3"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309"
-  dependencies:
-    debug "2.6.7"
-    depd "~1.1.0"
-    destroy "~1.0.4"
-    encodeurl "~1.0.1"
-    escape-html "~1.0.3"
-    etag "~1.8.0"
-    fresh "0.5.0"
-    http-errors "~1.6.1"
-    mime "1.3.4"
-    ms "2.0.0"
-    on-finished "~2.3.0"
-    range-parser "~1.2.0"
-    statuses "~1.3.1"
+semver@~5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
 
 send@0.16.1:
   version "0.16.1"
@@ -6600,25 +6490,16 @@ send@0.16.1:
     statuses "~1.3.1"
 
 serve-index@^1.7.2:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7"
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
   dependencies:
-    accepts "~1.3.3"
+    accepts "~1.3.4"
     batch "0.6.1"
-    debug "2.6.8"
-    escape-html "~1.0.3"
-    http-errors "~1.6.1"
-    mime-types "~2.1.15"
-    parseurl "~1.3.1"
-
-serve-static@1.12.3:
-  version "1.12.3"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"
-  dependencies:
-    encodeurl "~1.0.1"
+    debug "2.6.9"
     escape-html "~1.0.3"
-    parseurl "~1.3.1"
-    send "0.15.3"
+    http-errors "~1.6.2"
+    mime-types "~2.1.17"
+    parseurl "~1.3.2"
 
 serve-static@1.13.1:
   version "1.13.1"
@@ -6650,10 +6531,11 @@ setprototypeof@1.1.0:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
 
 sha.js@^2.4.0, sha.js@^2.4.8:
-  version "2.4.8"
-  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"
+  version "2.4.9"
+  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d"
   dependencies:
     inherits "^2.0.1"
+    safe-buffer "^5.0.1"
 
 shallow-clone@^0.1.2:
   version "0.1.2"
@@ -6704,6 +6586,12 @@ sntp@1.x.x:
   dependencies:
     hoek "2.x.x"
 
+sntp@2.x.x:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
+  dependencies:
+    hoek "4.x.x"
+
 sockjs-client@1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12"
@@ -6732,10 +6620,6 @@ source-list-map@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
 
-source-list-map@~0.1.7:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
-
 source-map-resolve@^0.3.0:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761"
@@ -6767,19 +6651,13 @@ source-map@^0.4.2, source-map@^0.4.4:
   dependencies:
     amdefine ">=0.0.4"
 
-source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
-  version "0.5.6"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
-
-source-map@^0.5.7:
+source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3, source-map@~0.5.6:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
 
-source-map@~0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
-  dependencies:
-    amdefine ">=0.0.4"
+source-map@^0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
 
 spdx-correct@~1.0.0:
   version "1.0.2"
@@ -6819,8 +6697,8 @@ spdy@^3.4.1:
     spdy-transport "^2.0.18"
 
 split@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae"
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
   dependencies:
     through "2"
 
@@ -6842,7 +6720,11 @@ sshpk@^1.7.0:
     jsbn "~0.1.0"
     tweetnacl "~0.14.0"
 
-"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
+"statuses@>= 1.3.1 < 2":
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+
+statuses@~1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
 
@@ -6889,8 +6771,8 @@ string-width@^1.0.1, string-width@^1.0.2:
     strip-ansi "^3.0.0"
 
 string-width@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0"
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
   dependencies:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
@@ -6905,7 +6787,7 @@ string_decoder@~1.0.3:
   dependencies:
     safe-buffer "~5.1.0"
 
-stringstream@~0.0.4:
+stringstream@~0.0.4, stringstream@~0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
 
@@ -6949,9 +6831,9 @@ strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
 
-style-loader@^0.18.2:
-  version "0.18.2"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.18.2.tgz#cc31459afbcd6d80b7220ee54b291a9fd66ff5eb"
+style-loader@^0.19.0:
+  version "0.19.0"
+  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.19.0.tgz#7258e788f0fee6a42d710eaf7d6c2412a4c50759"
   dependencies:
     loader-utils "^1.0.2"
     schema-utils "^0.3.0"
@@ -6976,21 +6858,9 @@ supports-color@^3.1.2, supports-color@^3.2.3:
   dependencies:
     has-flag "^1.0.0"
 
-supports-color@^4.0.0, supports-color@^4.1.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037"
-  dependencies:
-    has-flag "^2.0.0"
-
-supports-color@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
-  dependencies:
-    has-flag "^2.0.0"
-
-supports-color@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
+supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
   dependencies:
     has-flag "^2.0.0"
 
@@ -7026,12 +6896,12 @@ table@^3.7.8:
     string-width "^2.0.0"
 
 tapable@^0.2.7:
-  version "0.2.7"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c"
+  version "0.2.8"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
 
 tar-pack@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
   dependencies:
     debug "^2.2.0"
     fstream "^1.0.10"
@@ -7086,9 +6956,13 @@ thunky@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
 
+time-stamp@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357"
+
 timers-browserify@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86"
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6"
   dependencies:
     setimmediate "^1.0.4"
 
@@ -7108,13 +6982,17 @@ to-arraybuffer@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
 
-to-fast-properties@^1.0.1, to-fast-properties@^1.0.3:
+to-fast-properties@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
 
-tough-cookie@^2.3.2, tough-cookie@~2.3.0:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
+to-fast-properties@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+
+tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
   dependencies:
     punycode "^1.4.1"
 
@@ -7170,8 +7048,8 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
 ua-parser-js@^0.7.9:
-  version "0.7.13"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be"
+  version "0.7.17"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
 
 uglify-js@^2.6, uglify-js@^2.8.29:
   version "2.8.29"
@@ -7269,10 +7147,6 @@ util@0.10.3, util@^0.10.3:
   dependencies:
     inherits "2.0.1"
 
-utils-merge@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
-
 utils-merge@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@@ -7300,10 +7174,6 @@ value-equal@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
 
-vary@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
-
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -7312,11 +7182,13 @@ vendors@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
 
-verror@1.3.6:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
+verror@1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
   dependencies:
-    extsprintf "1.0.2"
+    assert-plus "^1.0.0"
+    core-util-is "1.0.2"
+    extsprintf "^1.2.0"
 
 vm-browserify@0.0.4:
   version "0.0.4"
@@ -7362,8 +7234,8 @@ webidl-conversions@^3.0.0:
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
 
 webidl-conversions@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0"
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
 
 webpack-bundle-analyzer@^2.8.3:
   version "2.9.0"
@@ -7382,17 +7254,18 @@ webpack-bundle-analyzer@^2.8.3:
     ws "^2.3.1"
 
 webpack-dev-middleware@^1.11.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.11.0.tgz#09691d0973a30ad1f82ac73a12e2087f0a4754f9"
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709"
   dependencies:
     memory-fs "~0.4.1"
     mime "^1.3.4"
     path-is-absolute "^1.0.0"
     range-parser "^1.0.3"
+    time-stamp "^2.0.0"
 
-webpack-dev-server@^2.6.1:
-  version "2.9.1"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.1.tgz#7ac9320b61b00eb65b2109f15c82747fc5b93585"
+webpack-dev-server@^2.9.3:
+  version "2.9.3"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.9.3.tgz#f0554e88d129e87796a6f74a016b991743ca6f81"
   dependencies:
     ansi-html "0.0.7"
     array-includes "^3.0.3"
@@ -7400,10 +7273,12 @@ webpack-dev-server@^2.6.1:
     chokidar "^1.6.0"
     compression "^1.5.2"
     connect-history-api-fallback "^1.3.0"
+    debug "^3.1.0"
     del "^3.0.0"
     express "^4.13.3"
     html-entities "^1.2.0"
     http-proxy-middleware "~0.17.4"
+    import-local "^0.1.1"
     internal-ip "1.2.0"
     ip "^1.1.5"
     loglevel "^1.4.1"
@@ -7432,13 +7307,6 @@ webpack-merge@^4.1.0:
   dependencies:
     lodash "^4.17.4"
 
-webpack-sources@^0.1.0:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
-  dependencies:
-    source-list-map "~0.1.7"
-    source-map "~0.5.3"
-
 webpack-sources@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
@@ -7446,9 +7314,9 @@ webpack-sources@^1.0.1:
     source-list-map "^2.0.0"
     source-map "~0.5.3"
 
-webpack@^3.4.1:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc"
+webpack@^3.8.1:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83"
   dependencies:
     acorn "^5.0.0"
     acorn-dynamic-import "^2.0.0"
@@ -7474,14 +7342,15 @@ webpack@^3.4.1:
     yargs "^8.0.2"
 
 websocket-driver@>=0.5.1:
-  version "0.6.5"
-  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
   dependencies:
+    http-parser-js ">=0.4.0"
     websocket-extensions ">=0.1.1"
 
 websocket-extensions@>=0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.1.tgz#76899499c184b6ef754377c2dbb0cd6cb55d29e7"
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.2.tgz#0e18781de629a18308ce1481650f67ffa2693a5d"
 
 websocket.js@^0.1.12:
   version "0.1.12"
@@ -7490,10 +7359,10 @@ websocket.js@^0.1.12:
     backoff "^2.4.1"
 
 whatwg-encoding@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4"
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3"
   dependencies:
-    iconv-lite "0.4.13"
+    iconv-lite "0.4.19"
 
 whatwg-fetch@>=0.10.0:
   version "2.0.3"
@@ -7518,13 +7387,7 @@ which-module@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
 
-which@1, which@^1.2.9:
-  version "1.2.14"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
-  dependencies:
-    isexe "^2.0.0"
-
-which@^1.2.12:
+which@1, which@^1.2.12, which@^1.2.9:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
   dependencies: