Просмотр исходного кода

Merge pull request #11877 from overleaf/mj-undefined-control-sequence-help

[web] Improve error messages around missing packages

GitOrigin-RevId: d6f59f452cdc8a5a7e59b9809f68bd18f629ba32
Alf Eaton 3 лет назад
Родитель
Сommit
31d0d10cfc

+ 3 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js

@@ -19,12 +19,13 @@ function PdfLogEntry({
   showCloseButton = false,
   showCloseButton = false,
   entryAriaLabel = null,
   entryAriaLabel = null,
   customClass,
   customClass,
+  contentDetails,
   onSourceLocationClick,
   onSourceLocationClick,
   onClose,
   onClose,
 }) {
 }) {
   if (ruleId && HumanReadableLogsHints[ruleId]) {
   if (ruleId && HumanReadableLogsHints[ruleId]) {
     const hint = HumanReadableLogsHints[ruleId]
     const hint = HumanReadableLogsHints[ruleId]
-    formattedContent = hint.formattedContent
+    formattedContent = hint.formattedContent(contentDetails)
     extraInfoURL = hint.extraInfoURL
     extraInfoURL = hint.extraInfoURL
   }
   }
 
 
@@ -84,6 +85,7 @@ PdfLogEntry.propTypes = {
   showSourceLocationLink: PropTypes.bool,
   showSourceLocationLink: PropTypes.bool,
   showCloseButton: PropTypes.bool,
   showCloseButton: PropTypes.bool,
   entryAriaLabel: PropTypes.string,
   entryAriaLabel: PropTypes.string,
+  contentDetails: PropTypes.arrayOf(PropTypes.string),
   onSourceLocationClick: PropTypes.func,
   onSourceLocationClick: PropTypes.func,
   onClose: PropTypes.func,
   onClose: PropTypes.func,
 }
 }

+ 1 - 0
services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js

@@ -53,6 +53,7 @@ function PdfLogsEntries({ entries, hasErrors }) {
           rawContent={logEntry.content}
           rawContent={logEntry.content}
           logType={logEntry.type}
           logType={logEntry.type}
           level={logEntry.level}
           level={logEntry.level}
+          contentDetails={logEntry.contentDetails}
           entryAriaLabel={t('log_entry_description', {
           entryAriaLabel={t('log_entry_description', {
             level: logEntry.level,
             level: logEntry.level,
           })}
           })}

+ 12 - 0
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogs.js

@@ -35,6 +35,18 @@ export default {
             ruleDetails.newMessage
             ruleDetails.newMessage
           )
           )
         }
         }
+        if (ruleDetails.contentRegex) {
+          const match = entry.content.match(ruleDetails.contentRegex)
+          if (match) {
+            entry.contentDetails = match.slice(1)
+          }
+        }
+        if (entry.contentDetails && ruleDetails.improvedTitle) {
+          entry.message = ruleDetails.improvedTitle(
+            entry.message,
+            entry.contentDetails
+          )
+        }
         // suppress any entries that are known to cascade from previous error types
         // suppress any entries that are known to cascade from previous error types
         if (ruleDetails.cascadesFrom != null) {
         if (ruleDetails.cascadesFrom != null) {
           for (type of ruleDetails.cascadesFrom) {
           for (type of ruleDetails.cascadesFrom) {

+ 61 - 42
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.js

@@ -1,4 +1,5 @@
 import PropTypes from 'prop-types'
 import PropTypes from 'prop-types'
+import packageSuggestions from './HumanReadableLogsPackageSuggestions'
 
 
 function WikiLink({ url, children }) {
 function WikiLink({ url, children }) {
   if (window.wikiEnabled) {
   if (window.wikiEnabled) {
@@ -21,7 +22,7 @@ const hints = {
   hint_misplaced_alignment_tab_character: {
   hint_misplaced_alignment_tab_character: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Misplaced_alignment_tab_character_%26',
       'https://www.overleaf.com/learn/Errors/Misplaced_alignment_tab_character_%26',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have placed an alignment tab character '&' in the wrong place. If
         You have placed an alignment tab character '&' in the wrong place. If
         you want to align something, you must write it inside an align
         you want to align something, you must write it inside an align
@@ -37,7 +38,7 @@ const hints = {
   hint_extra_alignment_tab_has_been_changed: {
   hint_extra_alignment_tab_has_been_changed: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Extra_alignment_tab_has_been_changed_to_%5Ccr',
       'https://www.overleaf.com/learn/Errors/Extra_alignment_tab_has_been_changed_to_%5Ccr',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have written too many alignment tabs in a table, causing one of them
         You have written too many alignment tabs in a table, causing one of them
         to be turned into a line break. Make sure you have specified the correct
         to be turned into a line break. Make sure you have specified the correct
@@ -49,7 +50,7 @@ const hints = {
   hint_display_math_should_end_with: {
   hint_display_math_should_end_with: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Display_math_should_end_with_$$',
       'https://www.overleaf.com/learn/Errors/Display_math_should_end_with_$$',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have forgotten a $ sign at the end of 'display math' mode. When
         You have forgotten a $ sign at the end of 'display math' mode. When
         writing in display math mode, you must always math write inside $$ … $$.
         writing in display math mode, you must always math write inside $$ … $$.
@@ -59,7 +60,7 @@ const hints = {
   },
   },
   hint_missing_inserted: {
   hint_missing_inserted: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Missing_$_inserted',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Missing_$_inserted',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         <p>
         <p>
           You need to enclose all mathematical expressions and symbols with
           You need to enclose all mathematical expressions and symbols with
@@ -82,7 +83,7 @@ const hints = {
   hint_reference_undefined: {
   hint_reference_undefined: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/There_were_undefined_references',
       'https://www.overleaf.com/learn/Errors/There_were_undefined_references',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have referenced something which has not yet been labelled. If you
         You have referenced something which has not yet been labelled. If you
         have labelled it already, make sure that what is written inside \ref
         have labelled it already, make sure that what is written inside \ref
@@ -94,7 +95,7 @@ const hints = {
   hint_there_were_undefined_references: {
   hint_there_were_undefined_references: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/There_were_undefined_references',
       'https://www.overleaf.com/learn/Errors/There_were_undefined_references',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have referenced something which has not yet been labelled. If you
         You have referenced something which has not yet been labelled. If you
         have labelled it already, make sure that what is written inside \ref
         have labelled it already, make sure that what is written inside \ref
@@ -106,7 +107,7 @@ const hints = {
   hint_citation_on_page_undefined_on_input_line: {
   hint_citation_on_page_undefined_on_input_line: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Citation_XXX_on_page_XXX_undefined_on_input_line_XXX',
       'https://www.overleaf.com/learn/Errors/Citation_XXX_on_page_XXX_undefined_on_input_line_XXX',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have cited something which is not included in your bibliography.
         You have cited something which is not included in your bibliography.
         Make sure that the citation (\cite
         Make sure that the citation (\cite
@@ -118,7 +119,7 @@ const hints = {
   hint_label_multiply_defined_labels: {
   hint_label_multiply_defined_labels: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/There_were_multiply-defined_labels',
       'https://www.overleaf.com/learn/Errors/There_were_multiply-defined_labels',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used the same label more than once. Check that each \label
         You have used the same label more than once. Check that each \label
         {'{...}'} labels only one item.
         {'{...}'} labels only one item.
@@ -128,7 +129,7 @@ const hints = {
   hint_float_specifier_changed: {
   hint_float_specifier_changed: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/%60!h%27_float_specifier_changed_to_%60!ht%27',
       'https://www.overleaf.com/learn/Errors/%60!h%27_float_specifier_changed_to_%60!ht%27',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         The float specifier 'h' is too strict of a demand for LaTeX to place
         The float specifier 'h' is too strict of a demand for LaTeX to place
         your float in a nice way here. Try relaxing it by using 'ht', or even
         your float in a nice way here. Try relaxing it by using 'ht', or even
@@ -144,7 +145,7 @@ const hints = {
   hint_no_positions_in_optional_float_specifier: {
   hint_no_positions_in_optional_float_specifier: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/No_positions_in_optional_float_specifier',
       'https://www.overleaf.com/learn/Errors/No_positions_in_optional_float_specifier',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have forgotten to include a float specifier, which tells LaTeX where
         You have forgotten to include a float specifier, which tells LaTeX where
         to position your figure. To fix this, either insert a float specifier
         to position your figure. To fix this, either insert a float specifier
@@ -163,20 +164,38 @@ const hints = {
   hint_undefined_control_sequence: {
   hint_undefined_control_sequence: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Undefined_control_sequence',
       'https://www.overleaf.com/learn/Errors/Undefined_control_sequence',
-    formattedContent: (
-      <>
-        The compiler is having trouble understanding a command you have used.
-        Check that the command is spelled correctly. If the command is part of a
-        package, make sure you have included the package in your preamble using
-        \usepackage
-        {'{...}'}.
-      </>
-    ),
+    formattedContent: details => {
+      if (details?.length && packageSuggestions.has(details[0])) {
+        const command = details[0]
+        const suggestion = packageSuggestions.get(command)
+        return (
+          <>
+            Undefined control sequence. You have used the command{' '}
+            <code>{command}</code>, which is part of the{' '}
+            <code>{suggestion.name}</code> package. Make sure to include the
+            package in your{' '}
+            <WikiLink url="https://www.overleaf.com/learn/latex/Learn_LaTeX_in_30_minutes#The_preamble_of_a_document">
+              preamble
+            </WikiLink>{' '}
+            using <code>{suggestion.command}</code>.
+          </>
+        )
+      }
+      return (
+        <>
+          The compiler is having trouble understanding a command you have used.
+          Check that the command is spelled correctly. If the command is part of
+          a package, make sure you have included the package in your preamble
+          using <code>\usepackage</code>
+          {'{...}'}.
+        </>
+      )
+    },
   },
   },
   hint_file_not_found: {
   hint_file_not_found: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/File_XXX_not_found_on_input_line_XXX',
       'https://www.overleaf.com/learn/Errors/File_XXX_not_found_on_input_line_XXX',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         The compiler cannot find the file you want to include. Make sure that
         The compiler cannot find the file you want to include. Make sure that
         you have{' '}
         you have{' '}
@@ -194,7 +213,7 @@ const hints = {
   hint_unknown_graphics_extension: {
   hint_unknown_graphics_extension: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_graphics_extension:_.XXX',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_graphics_extension:_.XXX',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         The compiler does not recognise the file type of one of your images.
         The compiler does not recognise the file type of one of your images.
         Make sure you are using a{' '}
         Make sure you are using a{' '}
@@ -209,7 +228,7 @@ const hints = {
   hint_unknown_float_option_h: {
   hint_unknown_float_option_h: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_float_option_%60H%27',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_float_option_%60H%27',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         The compiler isn't recognizing the float option 'H'. Include \usepackage
         The compiler isn't recognizing the float option 'H'. Include \usepackage
         {'{float}'} in your preamble to fix this.
         {'{float}'} in your preamble to fix this.
@@ -219,7 +238,7 @@ const hints = {
   hint_unknown_float_option_q: {
   hint_unknown_float_option_q: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_float_option_%60q%27',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Unknown_float_option_%60q%27',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a float specifier which the compiler does not understand.
         You have used a float specifier which the compiler does not understand.
         You can learn more about the different float options available for
         You can learn more about the different float options available for
@@ -234,7 +253,7 @@ const hints = {
   hint_math_allowed_only_in_math_mode: {
   hint_math_allowed_only_in_math_mode: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_%5Cmathrm_allowed_only_in_math_mode',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_%5Cmathrm_allowed_only_in_math_mode',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a font command which is only available in math mode. To
         You have used a font command which is only available in math mode. To
         use this command, you must be in maths mode (E.g. $ … $ or \begin
         use this command, you must be in maths mode (E.g. $ … $ or \begin
@@ -246,7 +265,7 @@ const hints = {
     ),
     ),
   },
   },
   hint_mismatched_environment: {
   hint_mismatched_environment: {
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used \begin
         You have used \begin
         {'{...}'} without a corresponding \end
         {'{...}'} without a corresponding \end
@@ -255,14 +274,14 @@ const hints = {
     ),
     ),
   },
   },
   hint_mismatched_brackets: {
   hint_mismatched_brackets: {
-    formattedContent: (
+    formattedContent: () => (
       <>You have used an open bracket without a corresponding close bracket.</>
       <>You have used an open bracket without a corresponding close bracket.</>
     ),
     ),
   },
   },
   hint_can_be_used_only_in_preamble: {
   hint_can_be_used_only_in_preamble: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Can_be_used_only_in_preamble',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Can_be_used_only_in_preamble',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a command in the main body of your document which should
         You have used a command in the main body of your document which should
         be used in the preamble. Make sure that \documentclass[…]
         be used in the preamble. Make sure that \documentclass[…]
@@ -275,7 +294,7 @@ const hints = {
   hint_missing_right_inserted: {
   hint_missing_right_inserted: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/Missing_%5Cright_insertede',
       'https://www.overleaf.com/learn/Errors/Missing_%5Cright_insertede',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have started an expression with a \left command, but have not
         You have started an expression with a \left command, but have not
         included a corresponding \right command. Make sure that your \left and
         included a corresponding \right command. Make sure that your \left and
@@ -290,7 +309,7 @@ const hints = {
   },
   },
   hint_double_superscript: {
   hint_double_superscript: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Double_superscript',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Double_superscript',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have written a double superscript incorrectly as a^b^c, or else you
         You have written a double superscript incorrectly as a^b^c, or else you
         have written a prime with a superscript. Remember to include {'{and}'}{' '}
         have written a prime with a superscript. Remember to include {'{and}'}{' '}
@@ -301,7 +320,7 @@ const hints = {
   },
   },
   hint_double_subscript: {
   hint_double_subscript: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Double_subscript',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Double_subscript',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have written a double subscript incorrectly as a_b_c. Remember to
         You have written a double subscript incorrectly as a_b_c. Remember to
         include {'{and}'} when using multiple subscripts. Try a_
         include {'{and}'} when using multiple subscripts. Try a_
@@ -311,7 +330,7 @@ const hints = {
   },
   },
   hint_no_author_given: {
   hint_no_author_given: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/No_%5Cauthor_given',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/No_%5Cauthor_given',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used the \maketitle command, but have not specified any
         You have used the \maketitle command, but have not specified any
         \author. To fix this, include an author in your preamble using the
         \author. To fix this, include an author in your preamble using the
@@ -323,7 +342,7 @@ const hints = {
   hint_environment_undefined: {
   hint_environment_undefined: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors%2FLaTeX%20Error%3A%20Environment%20XXX%20undefined',
       'https://www.overleaf.com/learn/Errors%2FLaTeX%20Error%3A%20Environment%20XXX%20undefined',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have created an environment (using \begin
         You have created an environment (using \begin
         {'{…}'} and \end
         {'{…}'} and \end
@@ -336,7 +355,7 @@ const hints = {
   hint_somethings_wrong_perhaps_a_missing_item: {
   hint_somethings_wrong_perhaps_a_missing_item: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Something%27s_wrong--perhaps_a_missing_%5Citem',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_Something%27s_wrong--perhaps_a_missing_%5Citem',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         There are no entries found in a list you have created. Make sure you
         There are no entries found in a list you have created. Make sure you
         label list entries using the \item command, and that you have not used a
         label list entries using the \item command, and that you have not used a
@@ -346,7 +365,7 @@ const hints = {
   },
   },
   hint_misplaced_noalign: {
   hint_misplaced_noalign: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Misplaced_%5Cnoalign',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Misplaced_%5Cnoalign',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a \hline command in the wrong place, probably outside a
         You have used a \hline command in the wrong place, probably outside a
         table. If the \hline command is written inside a table, try including \\
         table. If the \hline command is written inside a table, try including \\
@@ -357,7 +376,7 @@ const hints = {
   hint_no_line_here_to_end: {
   hint_no_line_here_to_end: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_There%27s_no_line_here_to_end',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_There%27s_no_line_here_to_end',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a \\ or \newline command where LaTeX was not expecting
         You have used a \\ or \newline command where LaTeX was not expecting
         one. Make sure that you only use line breaks after blocks of text, and
         one. Make sure that you only use line breaks after blocks of text, and
@@ -368,7 +387,7 @@ const hints = {
   hint_verb_ended_by_end_of_line: {
   hint_verb_ended_by_end_of_line: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_%5Cverb_ended_by_end_of_line',
       'https://www.overleaf.com/learn/Errors/LaTeX_Error:_%5Cverb_ended_by_end_of_line',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used a \verb command incorrectly. Try replacling the \verb
         You have used a \verb command incorrectly. Try replacling the \verb
         command with \begin
         command with \begin
@@ -382,7 +401,7 @@ const hints = {
   hint_illegal_unit_of_measure_pt_inserted: {
   hint_illegal_unit_of_measure_pt_inserted: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors%2FIllegal%20unit%20of%20measure%20(pt%20inserted)',
       'https://www.overleaf.com/learn/Errors%2FIllegal%20unit%20of%20measure%20(pt%20inserted)',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have written a length, but have not specified the appropriate units
         You have written a length, but have not specified the appropriate units
         (pt, mm, cm etc.). If you have not written a length, check that you have
         (pt, mm, cm etc.). If you have not written a length, check that you have
@@ -392,7 +411,7 @@ const hints = {
   },
   },
   hint_extra_right: {
   hint_extra_right: {
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Extra_%5Cright',
     extraInfoURL: 'https://www.overleaf.com/learn/Errors/Extra_%5Cright',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have written a \right command without a corresponding \left command.
         You have written a \right command without a corresponding \left command.
         Check that all \left and \right commands balance everywhere.
         Check that all \left and \right commands balance everywhere.
@@ -402,7 +421,7 @@ const hints = {
   hint_missing_begin_document_: {
   hint_missing_begin_document_: {
     extraInfoURL:
     extraInfoURL:
       'https://www.overleaf.com/learn/Errors%2FLaTeX%20Error%3A%20Missing%20%5Cbegin%20document',
       'https://www.overleaf.com/learn/Errors%2FLaTeX%20Error%3A%20Missing%20%5Cbegin%20document',
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         No \begin
         No \begin
         {'{document}'} command was found. Make sure you have included \begin
         {'{document}'} command was found. Make sure you have included \begin
@@ -412,7 +431,7 @@ const hints = {
     ),
     ),
   },
   },
   hint_mismatched_environment2: {
   hint_mismatched_environment2: {
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used \begin
         You have used \begin
         {'{}'} without a corresponding \end
         {'{}'} without a corresponding \end
@@ -421,7 +440,7 @@ const hints = {
     ),
     ),
   },
   },
   hint_mismatched_environment3: {
   hint_mismatched_environment3: {
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used \begin
         You have used \begin
         {'{}'} without a corresponding \end
         {'{}'} without a corresponding \end
@@ -430,7 +449,7 @@ const hints = {
     ),
     ),
   },
   },
   hint_mismatched_environment4: {
   hint_mismatched_environment4: {
-    formattedContent: (
+    formattedContent: () => (
       <>
       <>
         You have used \begin
         You have used \begin
         {'{}'} without a corresponding \end
         {'{}'} without a corresponding \end

+ 7 - 0
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsPackageSuggestions.js

@@ -0,0 +1,7 @@
+const suggestions = [
+  [
+    '\\includegraphics',
+    { name: 'graphicx', command: '\\usepackage{graphicx}' },
+  ],
+]
+export default new Map(suggestions)

+ 10 - 0
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsRules.js

@@ -1,4 +1,5 @@
 /* eslint-disable no-useless-escape */
 /* eslint-disable no-useless-escape */
+import packageSuggestions from './HumanReadableLogsPackageSuggestions'
 
 
 const rules = [
 const rules = [
   {
   {
@@ -44,6 +45,15 @@ const rules = [
   {
   {
     ruleId: 'hint_undefined_control_sequence',
     ruleId: 'hint_undefined_control_sequence',
     regexToMatch: /Undefined control sequence/,
     regexToMatch: /Undefined control sequence/,
+    contentRegex: /^l\.[0-9]+\s*(\\\S+)/,
+    improvedTitle: (currentTitle, details) => {
+      if (details?.length && packageSuggestions.has(details[0])) {
+        const command = details[0]
+        const suggestion = packageSuggestions.get(command)
+        return `${suggestion.command} is missing.`
+      }
+      return currentTitle
+    },
   },
   },
   {
   {
     ruleId: 'hint_file_not_found',
     ruleId: 'hint_file_not_found',