From 2ad17039f12267bc9e3640e0db4c667a381ac898 Mon Sep 17 00:00:00 2001
From: Mathias Jakobsen
Date: Tue, 16 Sep 2025 12:28:19 +0100
Subject: [PATCH] Merge pull request #28441 from overleaf/dp-4-way-test
Initial setup for 4 way split test
GitOrigin-RevId: c15496111b32e1b9bf0b7798d9d8ff79d229b1c8
---
.../web/frontend/extracted-translations.json | 5 +
.../components/error-logs/error-logs.tsx | 34 +++-
.../pdf-preview/pdf-logs-viewer.tsx | 19 ++
.../pdf-preview-hybrid-toolbar.tsx | 6 +-
.../ide-redesign/components/rail/rail.tsx | 14 +-
.../ide-redesign/utils/new-editor-utils.ts | 22 ++-
.../components/pdf-preview-pane.tsx | 12 +-
.../pdf-preview/hooks/use-log-events.ts | 167 ++++++++++--------
.../preview-logs-pane-max-entries.tsx | 8 +-
.../stylesheets/pages/editor/logs.scss | 29 +--
services/web/locales/en.json | 3 +
11 files changed, 213 insertions(+), 106 deletions(-)
create mode 100644 services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-logs-viewer.tsx
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 7d882c14dd..b2b53ebac0 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -121,8 +121,10 @@
"ai_feedback_the_suggestion_wasnt_the_best_fix_available": "",
"ai_feedback_there_was_no_code_fix_suggested": "",
"alignment": "",
+ "all": "",
"all_borders": "",
"all_features_in_group_standard_plus": "",
+ "all_logs": "",
"all_premium_features": "",
"all_premium_features_including": "",
"all_projects": "",
@@ -551,6 +553,7 @@
"error_opening_document_detail": "",
"error_performing_request": "",
"error_processing_file": "",
+ "errors": "",
"essential_cookies_only": "",
"example_project": "",
"existing_plan_active_until_term_end": "",
@@ -833,6 +836,7 @@
"included_as_part_of_your_writefull_subscription": "",
"increase_indent": "",
"increased_compile_timeout": "",
+ "info": "",
"initiator": "",
"inline": "",
"inline_math": "",
@@ -2091,6 +2095,7 @@
"visual_editor": "",
"visual_editor_is_only_available_for_tex_files": "",
"want_change_to_apply_before_plan_end": "",
+ "warnings": "",
"we_are_unable_to_opt_you_into_this_experiment": "",
"we_cant_find_any_sections_or_subsections_in_this_file": "",
"we_do_not_share_personal_information": "",
diff --git a/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx
index be4ba09660..84f19a88fc 100644
--- a/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/error-logs/error-logs.tsx
@@ -14,6 +14,9 @@ import LogEntry from './log-entry'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
import TimeoutUpgradePromptNew from '@/features/pdf-preview/components/timeout-upgrade-prompt-new'
import getMeta from '@/utils/meta'
+import PdfClearCacheButton from '@/features/pdf-preview/components/pdf-clear-cache-button'
+import PdfDownloadFilesButton from '@/features/pdf-preview/components/pdf-download-files-button'
+import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
const logsComponents: Array<{
import: { default: ElementType }
@@ -26,24 +29,32 @@ type ErrorLogTab = {
entries: LogEntryData[] | undefined
}
-function ErrorLogs() {
+function ErrorLogs({
+ includeActionButtons,
+}: {
+ includeActionButtons?: boolean
+}) {
const { error, logEntries, rawLog, validationIssues, stoppedOnFirstError } =
useCompileContext()
const { compileTimeout } = getMeta('ol-compileSettings')
+ const newLogsPosition = useIsNewErrorLogsPositionEnabled()
+ const { t } = useTranslation()
const tabs = useMemo(() => {
return [
- { key: 'all', label: 'All', entries: logEntries?.all },
- { key: 'errors', label: 'Errors', entries: logEntries?.errors },
- { key: 'warnings', label: 'Warnings', entries: logEntries?.warnings },
- { key: 'info', label: 'Info', entries: logEntries?.typesetting },
+ {
+ key: 'all',
+ label: newLogsPosition ? t('all') : t('all_logs'),
+ entries: logEntries?.all,
+ },
+ { key: 'errors', label: t('errors'), entries: logEntries?.errors },
+ { key: 'warnings', label: t('warnings'), entries: logEntries?.warnings },
+ { key: 'info', label: t('info'), entries: logEntries?.typesetting },
]
- }, [logEntries])
+ }, [logEntries, newLogsPosition, t])
const { loadingError } = usePdfPreviewContext()
- const { t } = useTranslation()
-
const [activeTab, setActiveTab] = useState('all')
const entries = useMemo(() => {
@@ -108,6 +119,13 @@ function ErrorLogs() {
showSourceLocationLink={false}
/>
)}
+
+ {includeActionButtons && (
+
+ )}
diff --git a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-logs-viewer.tsx b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-logs-viewer.tsx
new file mode 100644
index 0000000000..32fa2d9c21
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-logs-viewer.tsx
@@ -0,0 +1,19 @@
+import classnames from 'classnames'
+import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
+import ErrorLogs from '../error-logs/error-logs'
+import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
+
+export default function PdfLogsViewer() {
+ const { showLogs } = useCompileContext()
+ const { loadingError } = usePdfPreviewContext()
+
+ return (
+
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx
index fb02c93d09..3b4c2d5866 100644
--- a/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar.tsx
@@ -6,11 +6,11 @@ import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybri
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button'
import PdfHybridLogsButton from '@/features/pdf-preview/components/pdf-hybrid-logs-button'
-import { useAreNewErrorLogsEnabled } from '../../utils/new-editor-utils'
+import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
function PdfPreviewHybridToolbar() {
const { t } = useTranslation()
- const newErrorLogs = useAreNewErrorLogsEnabled()
+ const newErrorLogsPosition = useIsNewErrorLogsPositionEnabled()
// TODO: add detached pdf logic
return (
@@ -20,7 +20,7 @@ function PdfPreviewHybridToolbar() {
>
- {!newErrorLogs &&
}
+ {!newErrorLogsPosition &&
}
diff --git a/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx
index 7f9170cefb..a5d43cfd88 100644
--- a/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx
+++ b/services/web/frontend/js/features/ide-redesign/components/rail/rail.tsx
@@ -29,7 +29,7 @@ import RailResizeHandle from './rail-resize-handle'
import RailModals from './rail-modals'
import RailOverflowDropdown from './rail-overflow-dropdown'
import useRailOverflow from '../../hooks/use-rail-overflow'
-import { useAreNewErrorLogsEnabled } from '../../utils/new-editor-utils'
+import { useIsNewErrorLogsPositionEnabled } from '../../utils/new-editor-utils'
export const RailLayout = () => {
const { sendEvent } = useEditorAnalytics()
@@ -45,7 +45,7 @@ export const RailLayout = () => {
const isHistoryView = view === 'history'
- const newErrorlogs = useAreNewErrorLogsEnabled()
+ const newErrorlogsPosition = useIsNewErrorLogsPositionEnabled()
const railTabs: RailElement[] = useMemo(
() => [
@@ -94,10 +94,16 @@ export const RailLayout = () => {
component:
,
indicator:
,
disabled: errorLogsDisabled,
- hide: !newErrorlogs,
+ hide: !newErrorlogsPosition,
},
],
- [t, features.trackChangesVisible, newErrorlogs, errorLogsDisabled, view]
+ [
+ t,
+ features.trackChangesVisible,
+ newErrorlogsPosition,
+ errorLogsDisabled,
+ view,
+ ]
)
const railActions: RailAction[] = useMemo(
diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
index ac43a1ea27..9a89d795ac 100644
--- a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
+++ b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
@@ -25,7 +25,8 @@ export const canUseNewEditorViaNewUserFeatureFlag = () => {
!canUseNewEditorViaPrimaryFeatureFlag() &&
isNewUser() &&
(newUserTestVariant === 'new-editor' ||
- newUserTestVariant === 'new-editor-old-logs')
+ newUserTestVariant === 'new-editor-old-logs' ||
+ newUserTestVariant === 'new-editor-new-logs-old-position')
)
}
@@ -36,7 +37,7 @@ export const canUseNewEditor = () => {
)
}
-const canUseNewLogs = () => {
+const canUseNewLogsPosition = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
const canUseNewLogsViaNewUserFeatureFlag =
isNewUser() && newUserTestVariant === 'new-editor'
@@ -46,6 +47,18 @@ const canUseNewLogs = () => {
)
}
+const canUseNewLogs = () => {
+ const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
+ const canUseNewLogsViaNewUserFeatureFlag =
+ isNewUser() &&
+ (newUserTestVariant === 'new-editor' ||
+ newUserTestVariant === 'new-editor-new-logs-old-position')
+
+ return (
+ canUseNewEditorViaPrimaryFeatureFlag() || canUseNewLogsViaNewUserFeatureFlag
+ )
+}
+
export const useIsNewEditorEnabledViaPrimaryFeatureFlag = () => {
const { userSettings } = useUserSettingsContext()
const hasAccess = canUseNewEditorViaPrimaryFeatureFlag()
@@ -60,6 +73,11 @@ export const useIsNewEditorEnabled = () => {
return hasAccess && enabled
}
+export const useIsNewErrorLogsPositionEnabled = () => {
+ const newEditorEnabled = useIsNewEditorEnabled()
+ return newEditorEnabled && canUseNewLogsPosition()
+}
+
export const useAreNewErrorLogsEnabled = () => {
const newEditorEnabled = useIsNewEditorEnabled()
return newEditorEnabled && canUseNewLogs()
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
index 00bdade03f..90f368960b 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
@@ -13,10 +13,12 @@ import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-er
import {
useAreNewErrorLogsEnabled,
useIsNewEditorEnabled,
+ useIsNewErrorLogsPositionEnabled,
} from '@/features/ide-redesign/utils/new-editor-utils'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import PdfCodeCheckFailedBanner from '@/features/ide-redesign/components/pdf-preview/pdf-code-check-failed-banner'
import getMeta from '@/utils/meta'
+import NewPdfLogsViewer from '@/features/ide-redesign/components/pdf-preview/pdf-logs-viewer'
function PdfPreviewPane() {
const { pdfUrl } = useCompileContext()
@@ -26,6 +28,8 @@ function PdfPreviewPane() {
})
const newEditor = useIsNewEditorEnabled()
const newErrorLogs = useAreNewErrorLogsEnabled()
+ const newErrorLogsPosition = useIsNewErrorLogsPositionEnabled()
+
const pdfPromotions = importOverleafModules('pdfPreviewPromotions') as {
import: { default: ElementType }
path: string
@@ -48,7 +52,13 @@ function PdfPreviewPane() {
- {newErrorLogs ? : }
+ {newErrorLogsPosition ? (
+
+ ) : newErrorLogs ? (
+
+ ) : (
+
+ )}
{pdfPromotions.map(({ import: { default: Component }, path }) => (
))}
diff --git a/services/web/frontend/js/features/pdf-preview/hooks/use-log-events.ts b/services/web/frontend/js/features/pdf-preview/hooks/use-log-events.ts
index 193c6c8c4f..ea64f1527c 100644
--- a/services/web/frontend/js/features/pdf-preview/hooks/use-log-events.ts
+++ b/services/web/frontend/js/features/pdf-preview/hooks/use-log-events.ts
@@ -1,79 +1,65 @@
-import { useCallback, useEffect } from 'react'
+import { useCallback } from 'react'
import { useLayoutContext } from '@/shared/context/layout-context'
-import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
+import {
+ useAreNewErrorLogsEnabled,
+ useIsNewErrorLogsPositionEnabled,
+} from '@/features/ide-redesign/utils/new-editor-utils'
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
import { useEditorContext } from '@/shared/context/editor-context'
+import useEventListener from '@/shared/hooks/use-event-listener'
/**
* This hook adds an event listener for events dispatched from the editor to the compile logs pane
*/
export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
const { pdfLayout, setView } = useLayoutContext()
- const newEditor = useIsNewEditorEnabled()
+ const newLogs = useAreNewErrorLogsEnabled()
+ const newLogsPosition = useIsNewErrorLogsPositionEnabled()
const { openTab: openRailTab } = useRailContext()
const { hasPremiumSuggestion } = useEditorContext()
- const handleViewCompileLogEntryEventOldEditor = useCallback(
- (event: Event) => {
- const { id, suggestFix } = (
- event as CustomEvent<{ id: string; suggestFix?: boolean }>
- ).detail
+ const selectLogOldLogs = useCallback((id: string, suggestFix: boolean) => {
+ window.setTimeout(() => {
+ const element = document.querySelector(
+ `.log-entry[data-log-entry-id="${id}"]`
+ )
- setShowLogs(true)
+ if (element) {
+ element.scrollIntoView({
+ block: 'start',
+ inline: 'nearest',
+ })
- if (pdfLayout === 'flat') {
- setView('pdf')
- }
+ if (suggestFix) {
+ // if they are paywalled, click that instead
+ const paywall = document.querySelector(
+ 'button[data-action="assistant-paywall-show"]'
+ )
- window.setTimeout(() => {
- const element = document.querySelector(
- `.log-entry[data-log-entry-id="${id}"]`
- )
-
- if (element) {
- element.scrollIntoView({
- block: 'start',
- inline: 'nearest',
- })
-
- if (suggestFix) {
- // if they are paywalled, click that instead
- const paywall = document.querySelector(
- 'button[data-action="assistant-paywall-show"]'
- )
-
- if (paywall) {
- paywall.scrollIntoView({
- block: 'start',
- inline: 'nearest',
- })
- paywall.click()
- } else {
- element
- .querySelector(
- 'button[data-action="suggest-fix"]'
- )
- ?.click()
- }
+ if (paywall) {
+ paywall.scrollIntoView({
+ block: 'start',
+ inline: 'nearest',
+ })
+ paywall.click()
+ } else {
+ element
+ .querySelector(
+ 'button[data-action="suggest-fix"]'
+ )
+ ?.click()
}
}
- })
- },
- [pdfLayout, setView, setShowLogs]
- )
-
- const handleViewCompileLogEntryEventNewEditor = useCallback(
- (event: Event) => {
- const { id, suggestFix, showPaywallIfOutOfSuggestions } = (
- event as CustomEvent<{
- id: string
- suggestFix?: boolean
- showPaywallIfOutOfSuggestions?: boolean
- }>
- ).detail
-
- openRailTab('errors')
+ }
+ })
+ }, [])
+ const selectLogNewLogs = useCallback(
+ (
+ id: string,
+ suggestFix: boolean,
+ showPaywallIfOutOfSuggestions: boolean
+ ) => {
window.setTimeout(() => {
const logEntry = document.querySelector(
`.log-entry[data-log-entry-id="${id}"]`
@@ -114,26 +100,59 @@ export const useLogEvents = (setShowLogs: (show: boolean) => void) => {
}
})
},
- [openRailTab, hasPremiumSuggestion]
+ [hasPremiumSuggestion]
)
- useEffect(() => {
- const listener = (event: Event) => {
- if (newEditor) {
- handleViewCompileLogEntryEventNewEditor(event)
+ const openNewLogsPosition = useCallback(() => {
+ openRailTab('errors')
+ }, [openRailTab])
+
+ const openOldLogsPosition = useCallback(() => {
+ setShowLogs(true)
+
+ if (pdfLayout === 'flat') {
+ setView('pdf')
+ }
+ }, [pdfLayout, setView, setShowLogs])
+
+ const handleViewCompileLogEntryEvent = useCallback(
+ (event: Event) => {
+ const { id, suggestFix, showPaywallIfOutOfSuggestions } = (
+ event as CustomEvent<{
+ id: string
+ suggestFix?: boolean
+ showPaywallIfOutOfSuggestions?: boolean
+ }>
+ ).detail
+
+ if (newLogsPosition) {
+ openNewLogsPosition()
} else {
- handleViewCompileLogEntryEventOldEditor(event)
+ openOldLogsPosition()
}
- }
- window.addEventListener('editor:view-compile-log-entry', listener)
+ if (newLogs) {
+ selectLogNewLogs(
+ id,
+ Boolean(suggestFix),
+ Boolean(showPaywallIfOutOfSuggestions)
+ )
+ } else {
+ selectLogOldLogs(id, Boolean(suggestFix))
+ }
+ },
+ [
+ openNewLogsPosition,
+ openOldLogsPosition,
+ selectLogNewLogs,
+ selectLogOldLogs,
+ newLogs,
+ newLogsPosition,
+ ]
+ )
- return () => {
- window.removeEventListener('editor:view-compile-log-entry', listener)
- }
- }, [
- handleViewCompileLogEntryEventNewEditor,
- handleViewCompileLogEntryEventOldEditor,
- newEditor,
- ])
+ useEventListener(
+ 'editor:view-compile-log-entry',
+ handleViewCompileLogEntryEvent
+ )
}
diff --git a/services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.tsx b/services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.tsx
index b1f43ed3e9..a80c89e494 100644
--- a/services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.tsx
+++ b/services/web/frontend/js/features/preview/components/preview-logs-pane-max-entries.tsx
@@ -5,7 +5,7 @@ import { useDetachCompileContext as useCompileContext } from '../../../shared/co
import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
import MaterialIcon from '@/shared/components/material-icon'
import PdfLogEntry from '@/features/pdf-preview/components/pdf-log-entry'
-import { useAreNewErrorLogsEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
+import { useIsNewErrorLogsPositionEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
function PreviewLogsPaneMaxEntries({
totalEntries,
@@ -40,7 +40,7 @@ function PreviewLogsPaneMaxEntriesContent({
}: {
hasErrors?: boolean
}) {
- const newLogs = useAreNewErrorLogsEnabled()
+ const newLogsPosition = useIsNewErrorLogsPositionEnabled()
const { t } = useTranslation()
const { startCompile, stoppedOnFirstError, setAnimateCompileDropdownArrow } =
useCompileContext()
@@ -79,7 +79,7 @@ function PreviewLogsPaneMaxEntriesContent({
/>{' '}
- {newLogs
+ {newLogsPosition
? t('log_entry_maximum_entries_see_full_logs_new')
: t('log_entry_maximum_entries_see_full_logs')}
@@ -92,7 +92,7 @@ function PreviewLogsPaneMaxEntriesContent({
{t('tip')}:
- {newLogs
+ {newLogsPosition
? t('log_entry_maximum_entries_see_full_logs_new')
: t('log_entry_maximum_entries_see_full_logs')}
diff --git a/services/web/frontend/stylesheets/pages/editor/logs.scss b/services/web/frontend/stylesheets/pages/editor/logs.scss
index 92b640a042..133e6add3e 100644
--- a/services/web/frontend/stylesheets/pages/editor/logs.scss
+++ b/services/web/frontend/stylesheets/pages/editor/logs.scss
@@ -177,6 +177,7 @@
.error-logs-tabs {
display: flex;
+ background-color: var(--bg-primary-themed);
}
.error-logs-tab-header {
@@ -204,6 +205,14 @@
}
}
+.new-logs-pane {
+ position: absolute;
+ inset: 0;
+ z-index: 11; // above the PDF viewer + controls
+ top: var(--toolbar-small-height);
+ background-color: var(--bg-secondary-themed);
+}
+
.logs-pane {
position: absolute;
inset: 0;
@@ -220,16 +229,6 @@
min-height: 100%;
}
- .logs-pane-actions {
- display: flex;
- flex-wrap: wrap;
- place-content: flex-end flex-end;
- padding: var(--spacing-03) 0;
- flex-grow: 1;
- align-items: flex-end;
- gap: var(--spacing-04);
- }
-
.log-entry {
border-radius: var(--border-radius-base);
overflow: hidden;
@@ -340,6 +339,16 @@
}
}
+.logs-pane-actions {
+ display: flex;
+ flex-wrap: wrap;
+ place-content: flex-end flex-end;
+ padding: var(--spacing-03) 0;
+ flex-grow: 1;
+ align-items: flex-end;
+ gap: var(--spacing-04);
+}
+
.log-entry-content {
&:empty {
display: none;
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index b48af8ec54..4a40e2b7f2 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -149,6 +149,7 @@
"all": "All",
"all_borders": "All borders",
"all_features_in_group_standard_plus": "All features in Group Standard, plus:",
+ "all_logs": "All logs",
"all_premium_features": "All premium features",
"all_premium_features_including": "All premium features, including:",
"all_prices_displayed_are_in_currency": "All prices displayed are in __recommendedCurrency__.",
@@ -708,6 +709,7 @@
"error_opening_document_detail": "Sorry, something went wrong opening this document. Please try again.",
"error_performing_request": "An error has occurred while performing your request.",
"error_processing_file": "Sorry, something went wrong processing this file. Please try again.",
+ "errors": "Errors",
"es": "Spanish",
"essential_cookies_only": "Essential cookies only",
"estimated_number_of_overleaf_users": "Estimated number of __appName__ users",
@@ -2641,6 +2643,7 @@
"visual_editor_is_only_available_for_tex_files": "Visual Editor is only available for TeX files",
"want_access_to_overleaf_premium_features_through_your_university": "Want access to __appName__ premium features through your university?",
"want_change_to_apply_before_plan_end": "If you wish this change to apply before the end of your current billing period, please contact us.",
+ "warnings": "Warnings",
"we_are_unable_to_opt_you_into_this_experiment": "We are unable to opt you into this experiment at this time, please ensure your organization has allowed this feature, or try again later.",
"we_cant_confirm_this_email": "We can’t confirm this email",
"we_cant_find_any_sections_or_subsections_in_this_file": "We can’t find any sections or subsections in this file",