Merge "UI: Replace paper-toggle-button with md-switch"
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index e7840bc..0d6b48e 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -831,9 +831,9 @@
 * @polymer/iron-resizable-behavior
 * @polymer/iron-selector
 * @polymer/iron-validatable-behavior
-* @polymer/marked-element
 * @polymer/neon-animation
 * @polymer/paper-behaviors
+* @polymer/paper-button
 * @polymer/paper-card
 * @polymer/paper-dropdown-menu
 * @polymer/paper-fab
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index a5aa473..d37aae0 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -3436,9 +3436,9 @@
 * @polymer/iron-resizable-behavior
 * @polymer/iron-selector
 * @polymer/iron-validatable-behavior
-* @polymer/marked-element
 * @polymer/neon-animation
 * @polymer/paper-behaviors
+* @polymer/paper-button
 * @polymer/paper-card
 * @polymer/paper-dropdown-menu
 * @polymer/paper-fab
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 1c77e4a..dab63f6 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -198,7 +198,7 @@
 --
 
 Deletes the given account if config `enableDelete` under
-link:#config-gerrit.html#accounts[accounts] section is enbaled.
+link:#config-gerrit.html#accounts[accounts] section is enabled.
 
 Currently only supporting self deletion (regardless of the way
 link:#account-id[\{account-id\}] is provided).
diff --git a/java/com/google/gerrit/server/config/PreferencesParserUtil.java b/java/com/google/gerrit/server/config/PreferencesParserUtil.java
index 3e39e25..d7f19b0 100644
--- a/java/com/google/gerrit/server/config/PreferencesParserUtil.java
+++ b/java/com/google/gerrit/server/config/PreferencesParserUtil.java
@@ -231,13 +231,13 @@
       my = new ArrayList<>();
     }
     if (my.isEmpty()) {
-      my.add(new MenuItem("Dashboard", "#/dashboard/self", null));
-      my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
-      my.add(new MenuItem("Edits", "#/q/has:edit", null));
-      my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
-      my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
-      my.add(new MenuItem("All Visible Changes", "#/q/is:visible", null));
-      my.add(new MenuItem("Groups", "#/settings/#Groups", null));
+      my.add(new MenuItem("Dashboard", "/dashboard/self", null));
+      my.add(new MenuItem("Draft Comments", "/q/has:draft", null));
+      my.add(new MenuItem("Edits", "/q/has:edit", null));
+      my.add(new MenuItem("Watched Changes", "/q/is:watched+is:open", null));
+      my.add(new MenuItem("Starred Changes", "/q/is:starred", null));
+      my.add(new MenuItem("All Visible Changes", "/q/is:visible", null));
+      my.add(new MenuItem("Groups", "/settings/#Groups", null));
     }
     return my;
   }
@@ -307,8 +307,9 @@
   private static List<MenuItem> my(Config cfg) {
     List<MenuItem> my = new ArrayList<>();
     for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
-      String url = my(cfg, subsection, KEY_URL, "#/");
-      String target = my(cfg, subsection, KEY_TARGET, url.startsWith("#") ? null : "_blank");
+      String url = my(cfg, subsection, KEY_URL, "/");
+      boolean isRelative = url.startsWith("#") || url.startsWith("/");
+      String target = my(cfg, subsection, KEY_TARGET, isRelative ? null : "_blank");
       my.add(new MenuItem(subsection, url, target, my(cfg, subsection, KEY_ID, null)));
     }
     return my;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index db038d1..20d42fb 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -57,13 +57,13 @@
     assertPrefs(o, GeneralPreferencesInfo.defaults(), "my", "changeTable");
     assertThat(o.my)
         .containsExactly(
-            new MenuItem("Dashboard", "#/dashboard/self", null),
-            new MenuItem("Draft Comments", "#/q/has:draft", null),
-            new MenuItem("Edits", "#/q/has:edit", null),
-            new MenuItem("Watched Changes", "#/q/is:watched+is:open", null),
-            new MenuItem("Starred Changes", "#/q/is:starred", null),
-            new MenuItem("All Visible Changes", "#/q/is:visible", null),
-            new MenuItem("Groups", "#/settings/#Groups", null));
+            new MenuItem("Dashboard", "/dashboard/self", null),
+            new MenuItem("Draft Comments", "/q/has:draft", null),
+            new MenuItem("Edits", "/q/has:edit", null),
+            new MenuItem("Watched Changes", "/q/is:watched+is:open", null),
+            new MenuItem("Starred Changes", "/q/is:starred", null),
+            new MenuItem("All Visible Changes", "/q/is:visible", null),
+            new MenuItem("Groups", "/settings/#Groups", null));
     assertThat(o.changeTable).isEmpty();
 
     GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
diff --git a/polygerrit-ui/app/api/suggestions.ts b/polygerrit-ui/app/api/suggestions.ts
index c0e8d27..8ea6302 100644
--- a/polygerrit-ui/app/api/suggestions.ts
+++ b/polygerrit-ui/app/api/suggestions.ts
@@ -128,4 +128,9 @@
   NO_SUGGESTION = 'NO_SUGGESTION',
   OUT_OF_RANGE = 'OUT_OF_RANGE',
   ERROR = 'ERROR',
+  FILE_TOO_LARGE = 'FILE_TOO_LARGE',
+  INVALID_RANGE = 'INVALID_RANGE',
+  UNSUPPORTED_FILE_TYPE = 'UNSUPPORTED_FILE_TYPE',
+  USER_QUOTA_EXCEEDED = 'USER_QUOTA_EXCEEDED',
+  OK_LOW_CONFIDENCE = 'OK_LOW_CONFIDENCE',
 }
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index 3524d2f..079ff7d 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -988,17 +988,9 @@
 
   // private but used in test
   createHeaderLink(linkObj: TopMenuItemInfo): MainHeaderLink {
-    // Delete target property due to complications of
-    // https://issues.gerritcodereview.com/issues/40006107
-    //
-    // The server tries to guess whether URL is a view within the UI.
-    // If not, it sets target='_blank' on the menu item. The server
-    // makes assumptions that work for the GWT UI, but not PolyGerrit,
-    // so we'll just disable it altogether for now.
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
-    const {target, ...headerLink} = {...linkObj};
+    const headerLink = {...linkObj};
 
-    // Normalize all urls to PolyGerrit style.
+    // Normalize all GWT style URLs to PolyGerrit style.
     if (headerLink.url.startsWith('#')) {
       headerLink.url = linkObj.url.slice(1);
     }
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
index b5e28be..dc4902a 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.ts
@@ -309,11 +309,11 @@
     assert.deepEqual(
       [
         {url: 'https://awesometown.com/#hashyhash', name: '', target: ''},
-        {url: 'url', name: '', target: '_blank'},
+        {url: '#/q/is:nice', name: '', target: '_blank'},
       ].map(element.createHeaderLink),
       [
-        {url: 'https://awesometown.com/#hashyhash', name: ''},
-        {url: 'url', name: ''},
+        {url: 'https://awesometown.com/#hashyhash', name: '', target: ''},
+        {url: '/q/is:nice', name: '', target: '_blank'},
       ]
     );
   });
@@ -428,6 +428,7 @@
         links: [
           {
             name: 'Manage',
+            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/index.html',
           },
         ],
@@ -449,12 +450,10 @@
         items: [
           {
             name: 'Project Settings',
-            target: '_blank',
             url: '/plugins/myplugin/${projectName}',
           },
           {
             name: 'Project List',
-            target: '_blank',
             url: '/plugins/myplugin/index.html',
           },
         ],
@@ -493,7 +492,6 @@
         items: [
           {
             name: 'Manage',
-            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/index.html',
           },
         ],
@@ -503,7 +501,6 @@
         items: [
           {
             name: 'Create',
-            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/create.html',
           },
         ],
@@ -550,7 +547,6 @@
         items: [
           {
             name: 'Manage',
-            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/index.html',
           },
         ],
@@ -589,7 +585,6 @@
         items: [
           {
             name: 'Manage',
-            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/index.html',
           },
         ],
@@ -652,6 +647,7 @@
           adminLinks[0],
           {
             name: 'Manage',
+            target: '_blank',
             url: 'https://gerrit/plugins/plugin-manager/static/index.html',
           },
         ],
diff --git a/polygerrit-ui/app/elements/settings/gr-preferences/gr-preferences.ts b/polygerrit-ui/app/elements/settings/gr-preferences/gr-preferences.ts
index 17c9f6d..cea96cf 100644
--- a/polygerrit-ui/app/elements/settings/gr-preferences/gr-preferences.ts
+++ b/polygerrit-ui/app/elements/settings/gr-preferences/gr-preferences.ts
@@ -124,13 +124,15 @@
       () => this.getConfigModel().docsBaseUrl$,
       docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
     );
-    subscribe(
-      this,
-      () => this.getPluginLoader().pluginsModel.suggestionsPlugins$,
-      // We currently support results from only 1 provider.
-      suggestionsPlugins =>
-        (this.suggestionsProvider = suggestionsPlugins?.[0]?.provider)
-    );
+    if (this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT_V2)) {
+      subscribe(
+        this,
+        () => this.getPluginLoader().pluginsModel.suggestionsPlugins$,
+        // We currently support results from only 1 provider.
+        suggestionsPlugins =>
+          (this.suggestionsProvider = suggestionsPlugins?.[0]?.provider)
+      );
+    }
   }
 
   static override get styles() {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index 3a585dd..e01fdf4 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -122,6 +122,9 @@
           /* Brings back the round corners we had with paper-button */
           --md-text-button-container-shape: 4px;
           --md-elevated-button-container-shape: 4px;
+          /* We have a variable for setting the text colour when it is disabled */
+          --md-elevated-button-disabled-label-text-color: var(--text-color);
+          --md-text-button-disabled-label-text-color: var(--text-color);
           align-items: center;
           background-color: var(--background-color);
           color: var(--text-color);
@@ -187,6 +190,9 @@
           min-width: 3em;
           --md-sys-color-primary: var(--vote-text-color);
         }
+        .loadingSpin {
+          display: inline-block;
+        }
       `,
     ];
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 7236aa2..36b5eda 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -446,16 +446,18 @@
         this.autocompleteComment();
       }
     );
-    subscribe(
-      this,
-      () =>
-        this.generateSuggestionTrigger$.pipe(
-          debounceTime(GENERATE_SUGGESTION_DEBOUNCE_DELAY_MS)
-        ),
-      () => {
-        this.generateSuggestEdit();
-      }
-    );
+    if (this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT_V2)) {
+      subscribe(
+        this,
+        () =>
+          this.generateSuggestionTrigger$.pipe(
+            debounceTime(GENERATE_SUGGESTION_DEBOUNCE_DELAY_MS)
+          ),
+        () => {
+          this.generateSuggestEdit();
+        }
+      );
+    }
     subscribe(
       this,
       () => this.getUserModel().preferences$,
@@ -1119,6 +1121,7 @@
   // private but used in test
   showGeneratedSuggestion() {
     return (
+      this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT_V2) &&
       this.getSuggestionsService().isGeneratedSuggestedFixEnabledForComment(
         this.comment
       ) &&
@@ -1226,6 +1229,7 @@
 
   private async generateSuggestEdit() {
     if (
+      !this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT_V2) ||
       !this.showGeneratedSuggestion() ||
       !this.generateSuggestion ||
       this.messageText.length === 0
@@ -1635,6 +1639,8 @@
   }
 
   getFixSuggestions(): FixSuggestionInfo[] | undefined {
+    if (!this.flagsService.isEnabled(KnownExperimentId.ML_SUGGESTED_EDIT_V2))
+      return undefined;
     if (!this.generateSuggestion) return undefined;
     if (!this.generatedFixSuggestion) return undefined;
     // Disable fix suggestions when the comment already has a user suggestion
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index ebd0788..d4537fa 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -46,6 +46,7 @@
   CommentsModel,
   commentsModelToken,
 } from '../../../models/comments/comments-model';
+import {KnownExperimentId} from '../../../services/flags/flags';
 import {GrSuggestionDiffPreview} from '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
 
 suite('gr-comment tests', () => {
@@ -891,6 +892,11 @@
         },
       ],
     };
+    setup(async () => {
+      stubFlags('isEnabled')
+        .withArgs(KnownExperimentId.ML_SUGGESTED_EDIT_V2)
+        .returns(true);
+    });
 
     test('renders suggestions in comment', async () => {
       const comment = {
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 87b9f72..2bf84fe7 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -11,7 +11,6 @@
   sanitizeHtmlToFragment,
 } from '../../../utils/inner-html-util';
 import {unescapeHTML} from '../../../utils/syntax-util';
-import '@polymer/marked-element';
 import {resolve} from '../../../models/dependency';
 import {subscribe} from '../../lit/subscription-controller';
 import {configModelToken} from '../../../models/config/config-model';
@@ -24,6 +23,7 @@
   USER_SUGGESTION_INFO_STRING,
 } from '../../../utils/comment-util';
 import {sameOrigin} from '../../../utils/url-util';
+import '../gr-marked-element/gr-marked-element';
 
 // MIME types for images we allow showing. Do not include SVG, it can contain
 // arbitrary JavaScript.
@@ -244,11 +244,11 @@
     const allowMarkdownBase64ImagesInComments =
       this.allowMarkdownBase64ImagesInComments;
 
-    // We are overriding some marked-element renderers for a few reasons:
+    // We are overriding some gr-marked-element renderers for a few reasons:
     // 1. Disable inline images as a design/policy choice.
     // 2. Inline code blocks ("codespan") do not unescape HTML characters when
     //    rendering without <pre> and so we must do this manually.
-    //    <marked-element> is already escaping these internally. See test
+    //    <gr-marked-element> is already escaping these internally. See test
     //    covering this.
     // 3. Multiline code blocks ("code") is similarly handling escaped
     //    characters using <pre>. The convention is to only use <pre> for multi-
@@ -304,7 +304,7 @@
           return `<pre><code>${text}</code></pre>`;
         }
       };
-      // <marked-element> internals will be in charge of calling our custom
+      // <gr-marked-element> internals will be in charge of calling our custom
       // renderer so we write these functions separately so that 'this' is
       // preserved via closure.
       renderer['paragraph'] = boundRewriteAsterisks;
@@ -313,10 +313,10 @@
 
     // The child with slot is optional but allows us control over the styling.
     // The `callback` property lets us do a final sanitization of the output
-    // HTML string before it is rendered by `<marked-element>` in case any
+    // HTML string before it is rendered by `<gr-marked-element>` in case any
     // rewrites have been abused to attempt an XSS attack.
     return html`
-      <marked-element
+      <gr-marked-element
         .markdown=${this.escapeAllButBlockQuotes(this.content)}
         .breaks=${true}
         .renderer=${customRenderer}
@@ -324,7 +324,7 @@
           sanitizeHtml(contents)}
       >
         <div class="markdown-html" slot="markdown-html"></div>
-      </marked-element>
+      </gr-marked-element>
     `;
   }
 
@@ -349,10 +349,21 @@
   }
 
   override updated() {
+    this.removeEventListener(
+      'marked-render-complete',
+      this.markedRenderComplete
+    );
+    // When masked-element was using Polymer, it was rendered synchronously,
+    // compared to the lit version of the element. updated() ran before the markdown
+    // was inserted into the slot.
+    this.addEventListener('marked-render-complete', this.markedRenderComplete);
+  }
+
+  readonly markedRenderComplete = () => {
     // Look for @mentions and replace them with an account-label chip.
     this.convertEmailsToAccountChips();
     this.convertCodeToSuggestions();
-  }
+  };
 
   private convertEmailsToAccountChips() {
     for (const emailLink of this.renderRoot.querySelectorAll(
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 1f751e5..113a6cd 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -307,7 +307,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>text</p>
                 <p>
@@ -342,7 +342,7 @@
                   </a>
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -409,7 +409,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <h1>h1-heading</h1>
                 <h2>h2-heading</h2>
@@ -438,7 +438,7 @@
                   </a>
                 </h1>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -454,7 +454,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>
                   <code>inline code</code>
@@ -466,7 +466,7 @@
                   <code>inline code with config link: LinkRewriteMe</code>
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -482,7 +482,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <pre>
               <code>multiline code</code>
@@ -494,7 +494,7 @@
               <code>multiline code with config link: LinkRewriteMe</code>
             </pre>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -508,11 +508,11 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>![img](google.com/img.png)</p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -526,13 +526,13 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>
                   <gr-account-chip></gr-account-chip>
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -554,7 +554,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>
                   <code>@</code>
@@ -567,7 +567,7 @@
                   </a>
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -588,7 +588,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>
                   <a
@@ -624,7 +624,7 @@
                   >
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -640,7 +640,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <blockquote>
                   <p>block quote</p>
@@ -670,7 +670,7 @@
                   </p>
                 </blockquote>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -689,7 +689,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>plain text ${escapedDiv}</p>
                 <p>
@@ -712,7 +712,7 @@
                   >
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -726,7 +726,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <blockquote>
                   <blockquote>
@@ -736,7 +736,7 @@
                   </blockquote>
                 </blockquote>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -757,7 +757,7 @@
         element,
         /* HTML */ `
           <gr-endpoint-decorator name="formatted-text-endpoint">
-            <marked-element>
+            <gr-marked-element>
               <div slot="markdown-html" class="markdown-html">
                 <p>
                   I think
@@ -769,7 +769,7 @@
                   >
                 </p>
               </div>
-            </marked-element>
+            </gr-marked-element>
           </gr-endpoint-decorator>
         `
       );
@@ -836,11 +836,11 @@
           /* HTML */
           `
             <gr-endpoint-decorator name="formatted-text-endpoint">
-              <marked-element>
+              <gr-marked-element>
                 <div class="markdown-html" slot="markdown-html">
                   <gr-user-suggestion-fix>Hello World</gr-user-suggestion-fix>
                 </div>
-              </marked-element>
+              </gr-marked-element>
             </gr-endpoint-decorator>
           `
         );
diff --git a/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element.ts b/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element.ts
new file mode 100644
index 0000000..5aeb2cb
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element.ts
@@ -0,0 +1,141 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {css, html, LitElement, PropertyValues} from 'lit';
+import {
+  customElement,
+  property,
+  queryAssignedElements,
+} from 'lit/decorators.js';
+// @ts-ignore
+import * as marked from 'marked/lib/marked';
+
+if (!window.marked) {
+  window.marked = marked;
+}
+
+declare global {
+  interface Window {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    marked: any;
+  }
+
+  interface HTMLElementTagNameMap {
+    'gr-marked-element': GrMarkedElement;
+  }
+}
+
+/**
+ * This is based on [marked-element](https://github.com/PolymerElements/marked-element) by Polymer
+ * but converted to use Lit. It uses the [marked](https://github.com/markedjs/marked) library.
+ */
+@customElement('gr-marked-element')
+export class GrMarkedElement extends LitElement {
+  @property({type: String}) markdown: string | null = null;
+
+  @property({type: Boolean}) breaks = false;
+
+  @property({type: Boolean}) pedantic = false;
+
+  @property({type: Function}) renderer: Function | null = null;
+
+  @property({type: Boolean}) sanitize = false;
+
+  @property({type: Function}) sanitizer:
+    | ((html: string) => string)
+    | undefined = undefined;
+
+  @property({type: Boolean}) smartypants = false;
+
+  @property({type: Function}) callback: Function | null = null;
+
+  @queryAssignedElements({
+    flatten: true,
+    slot: 'markdown-html',
+  })
+  private outputElement!: Array<HTMLElement>;
+
+  static override styles = css`
+    :host {
+      display: block;
+    }
+  `;
+
+  override render() {
+    return html`
+      <slot name="markdown-html">
+        <div id="content" slot="markdown-html"></div>
+      </slot>
+    `;
+  }
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this.renderMarkdown();
+  }
+
+  protected override updated(changedProps: PropertyValues) {
+    const propsToWatch = [
+      'markdown',
+      'breaks',
+      'pedantic',
+      'renderer',
+      'sanitize',
+      'sanitizer',
+      'smartypants',
+      'callback',
+    ];
+
+    if (propsToWatch.some(prop => changedProps.has(prop))) {
+      this.renderMarkdown();
+    }
+  }
+
+  override firstUpdated() {
+    this.renderMarkdown();
+  }
+
+  private renderMarkdown() {
+    if (!this.isConnected || !this.outputElement.length) {
+      return;
+    }
+
+    if (!this.markdown) {
+      this.outputElement[0].innerHTML = '';
+      return;
+    }
+
+    const renderer = new window.marked.Renderer();
+    if (this.renderer) this.renderer(renderer);
+
+    const options = {
+      renderer,
+      highlight: this.highlight.bind(this),
+      breaks: this.breaks,
+      sanitize: this.sanitize,
+      sanitizer: this.sanitizer,
+      pedantic: this.pedantic,
+      smartypants: this.smartypants,
+    };
+
+    const output = window.marked(this.markdown, options, this.callback);
+
+    this.outputElement[0].innerHTML = output;
+    this.dispatchEvent(
+      new CustomEvent('marked-render-complete', {bubbles: true, composed: true})
+    );
+  }
+
+  private highlight(code: string, lang: string): string {
+    const event = new CustomEvent('syntax-highlight', {
+      detail: {code, lang},
+      bubbles: true,
+      composed: true,
+    });
+    this.dispatchEvent(event);
+    return event.detail.code || code;
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element_test.ts b/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element_test.ts
new file mode 100644
index 0000000..01b2d0f
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-marked-element/gr-marked-element_test.ts
@@ -0,0 +1,31 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../../test/common-test-setup';
+import './gr-marked-element';
+import {assert, fixture, html} from '@open-wc/testing';
+import {GrMarkedElement} from './gr-marked-element';
+
+suite('gr-app-element tests', () => {
+  let element: GrMarkedElement;
+
+  setup(async () => {
+    element = await fixture<GrMarkedElement>(
+      html`<gr-marked-element></gr-marked-element>`
+    );
+    await element.updateComplete;
+  });
+
+  test('renders', () => {
+    assert.shadowDom.equal(
+      element,
+      /* HTML */ `
+        <slot name="markdown-html">
+          <div id="content" slot="markdown-html"></div>
+        </slot>
+      `
+    );
+  });
+});
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index cfb850d..82465ad 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -3,7 +3,7 @@
  * Copyright 2021 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import '@material/web/button/text-button';
+import '@polymer/paper-button/paper-button';
 import '@polymer/paper-tooltip/paper-tooltip';
 import {EMPTY, of, Subject} from 'rxjs';
 import {delay, switchMap} from 'rxjs/operators';
@@ -200,12 +200,10 @@
           font: var(--context-control-button-font, inherit);
         }
 
-        md-text-button {
+        paper-button {
           text-transform: none;
           align-items: center;
           background-color: var(--background-color);
-          --md-text-button-label-text-font: inherit;
-          /* This is also set in the button-label-font css var above. We keep this incase it is also needed. */
           font-family: inherit;
           margin: var(--margin, 0);
           min-width: var(--border, 0);
@@ -214,26 +212,13 @@
           border-width: 1px;
           border-radius: var(--border-radius);
           padding: var(--spacing-s) var(--spacing-l);
-          --md-text-button-container-color: var(--background-color);
-          --md-sys-color-primary: var(--diff-context-control-color);
-          /* We set this to 0 which defaults to 12px under text-button.
-            This is because for some reason it makes the size of it bigger
-            which makes some of the buttons look wrong. E.g. attention set has
-            a bigger width and thus a lot of space. This fixes it. */
-          --md-text-button-leading-space: 0;
-          --md-text-button-trailing-space: 0;
-          /* Brings back the round corners we had with paper-button */
-          --md-text-button-container-shape: 4px;
-          /* Needed to resize properly */
-          min-height: auto;
-          height: auto;
         }
 
-        md-text-button:hover {
+        paper-button:hover {
           /* same as defined in gr-button */
           background: rgba(0, 0, 0, 0.12);
         }
-        md-text-button:focus-visible {
+        paper-button:focus-visible {
           /* paper-button sets this to 0, thus preventing focus-based styling. */
           outline-width: 1px;
         }
@@ -457,7 +442,7 @@
       });
     };
 
-    const button = html` <md-text-button
+    const button = html` <paper-button
       class=${classes}
       aria-label=${ariaLabel}
       @click=${expandHandler}
@@ -466,7 +451,7 @@
     >
       <span class="showContext">${text}</span>
       ${tooltip}
-    </md-text-button>`;
+    </paper-button>`;
     return button;
   }
 
diff --git a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
index a65a11e..b08a2e8 100644
--- a/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
@@ -33,7 +33,7 @@
     await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
-      'md-text-button.showContext'
+      'paper-button.showContext'
     );
     assert.equal(buttons.length, 1);
     assert.equal(buttons[0].textContent!.trim(), '+10 common lines');
@@ -45,7 +45,7 @@
     await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
-      'md-text-button.showContext'
+      'paper-button.showContext'
     );
 
     assert.equal(buttons.length, 2);
@@ -62,7 +62,7 @@
     await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
-      'md-text-button.showContext'
+      'paper-button.showContext'
     );
 
     assert.equal(buttons.length, 3);
@@ -81,7 +81,7 @@
     await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
-      'md-text-button.showContext'
+      'paper-button.showContext'
     );
 
     assert.equal(buttons.length, 2);
@@ -104,13 +104,13 @@
     await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.fullExpansion md-text-button'
+      '.fullExpansion paper-button'
     );
     const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.partialExpansion md-text-button'
+      '.partialExpansion paper-button'
     );
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(fullExpansionButtons.length, 1);
     assert.equal(partialExpansionButtons.length, 1);
@@ -132,13 +132,13 @@
     await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.fullExpansion md-text-button'
+      '.fullExpansion paper-button'
     );
     const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.partialExpansion md-text-button'
+      '.partialExpansion paper-button'
     );
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(fullExpansionButtons.length, 1);
     assert.equal(partialExpansionButtons.length, 2);
@@ -168,13 +168,13 @@
     await waitEventLoop();
 
     const fullExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.fullExpansion md-text-button'
+      '.fullExpansion paper-button'
     );
     const partialExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.partialExpansion md-text-button'
+      '.partialExpansion paper-button'
     );
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(fullExpansionButtons.length, 1);
     assert.equal(partialExpansionButtons.length, 1);
@@ -207,7 +207,7 @@
     await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(
       blockExpansionButtons[0]
@@ -258,7 +258,7 @@
     await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(
       blockExpansionButtons[0]
@@ -302,7 +302,7 @@
     await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     assert.equal(
       blockExpansionButtons[0]
@@ -319,7 +319,7 @@
     await waitEventLoop();
 
     const blockExpansionButtons = element.shadowRoot!.querySelectorAll(
-      '.blockExpansion md-text-button'
+      '.blockExpansion paper-button'
     );
     const tooltipAbove =
       blockExpansionButtons[0].querySelector('paper-tooltip')!;
@@ -342,7 +342,7 @@
     await waitEventLoop();
 
     const buttons = element.shadowRoot!.querySelectorAll(
-      'md-text-button.showContext'
+      'paper-button.showContext'
     );
     assert.equal(buttons.length, 1);
     assert.equal(buttons[0].textContent!.trim(), '+ Unrelated changes');
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index 7b99808..e7bcdb79 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -213,10 +213,6 @@
     license: SharedLicenses.Polymer2015,
   },
   {
-    name: '@polymer/marked-element',
-    license: SharedLicenses.Polymer2015,
-  },
-  {
     name: '@polymer/neon-animation',
     license: SharedLicenses.Polymer2015,
   },
@@ -225,6 +221,10 @@
     license: SharedLicenses.Polymer2015,
   },
   {
+    name: '@polymer/paper-button',
+    license: SharedLicenses.Polymer2015,
+  },
+  {
     name: '@polymer/paper-card',
     license: SharedLicenses.Polymer2015,
   },
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 55504dc..81a98cb 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -13,7 +13,7 @@
     "@polymer/iron-iconset-svg": "^3.0.1",
     "@polymer/iron-input": "^3.0.1",
     "@polymer/iron-selector": "^3.0.1",
-    "@polymer/marked-element": "^3.0.1",
+    "@polymer/paper-button": "^3.0.1",
     "@polymer/paper-card": "^3.0.1",
     "@polymer/paper-checkbox": "^3.1.0",
     "@polymer/paper-dropdown-menu": "^3.2.0",
@@ -36,6 +36,7 @@
     "highlightjs-vue": "https://github.com/paladox/highlightjs-vue#44eed074ea0110d1ad03d2cbd77d27027cf7bb04",
     "immer": "^9.0.21",
     "lit": "^3.2.1",
+    "marked": "^0.5.0",
     "polymer-bridges": "file:../../polymer-bridges",
     "polymer-resin": "^2.0.1",
     "resemblejs": "^5.0.0",
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 144b260..86d8a39 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -18,6 +18,7 @@
   NEW_IMAGE_DIFF_UI = 'UiFeature__new_image_diff_ui',
   CHECKS_DEVELOPER = 'UiFeature__checks_developer',
   PUSH_NOTIFICATIONS_DEVELOPER = 'UiFeature__push_notifications_developer',
+  ML_SUGGESTED_EDIT_V2 = 'UiFeature__ml_suggested_edit_v2',
   REVISION_PARENTS_DATA = 'UiFeature__revision_parents_data',
   COMMENT_AUTOCOMPLETION = 'UiFeature__comment_autocompletion_enabled',
   SAVE_PROJECT_CONFIG_FOR_REVIEW = 'UiFeature__save_project_config_for_review',
diff --git a/polygerrit-ui/app/types/common.ts b/polygerrit-ui/app/types/common.ts
index 536ebdf..6c577c1 100644
--- a/polygerrit-ui/app/types/common.ts
+++ b/polygerrit-ui/app/types/common.ts
@@ -518,7 +518,7 @@
 export interface TopMenuItemInfo {
   url: string;
   name: string;
-  target: string;
+  target?: string;
   id?: string;
 }
 
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index ab5b397..4ea578f 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -208,14 +208,6 @@
     "@polymer/iron-meta" "^3.0.0-pre.26"
     "@polymer/polymer" "^3.0.0"
 
-"@polymer/marked-element@^3.0.1":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@polymer/marked-element/-/marked-element-3.0.1.tgz#56add62080404dea142c055977807ae9ca773a89"
-  integrity sha512-WJQzQetxdStVGQbyTBUBgd+hSI0Rl39uJg7b2zL3r6EfMnibzmA/YNT06M8jVZdxPF+B4SumrFWRtasVtGQRUQ==
-  dependencies:
-    "@polymer/polymer" "^3.0.0"
-    marked "~0.3.9"
-
 "@polymer/neon-animation@^3.0.0-pre.26":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@polymer/neon-animation/-/neon-animation-3.0.1.tgz#6658e4b524abc057477772a7473292493d366c24"
@@ -235,6 +227,16 @@
     "@polymer/paper-ripple" "^3.0.0-pre.26"
     "@polymer/polymer" "^3.0.0"
 
+"@polymer/paper-button@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@polymer/paper-button/-/paper-button-3.0.1.tgz#f13b019137e3f6ccc4d04d0b1f27f4830ea5774d"
+  integrity sha512-JRNBc+Oj9EWnmyLr7FcCr8T1KAnEHPh6mosln9BUdkM+qYaYsudSICh3cjTIbnj6AuF5OJidoLkM1dlyj0j6Zg==
+  dependencies:
+    "@polymer/iron-flex-layout" "^3.0.0-pre.26"
+    "@polymer/paper-behaviors" "^3.0.0-pre.27"
+    "@polymer/paper-styles" "^3.0.0-pre.26"
+    "@polymer/polymer" "^3.0.0"
+
 "@polymer/paper-card@^3.0.1":
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/@polymer/paper-card/-/paper-card-3.0.1.tgz#fb5960b3e55fab56d20b7c1c3dee08f0d052ff2a"
@@ -659,10 +661,10 @@
   dependencies:
     semver "^6.0.0"
 
-marked@~0.3.9:
-  version "0.3.19"
-  resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
-  integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
+marked@^0.5.0:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/marked/-/marked-0.5.2.tgz#3efdb27b1fd0ecec4f5aba362bddcd18120e5ba9"
+  integrity sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==
 
 mimic-response@^2.0.0:
   version "2.1.0"