Merge "Switch the change view to listening to the view model"
diff --git a/Documentation/config-groups.txt b/Documentation/config-groups.txt
index 0917515..4abb223 100644
--- a/Documentation/config-groups.txt
+++ b/Documentation/config-groups.txt
@@ -34,7 +34,7 @@
 group, there is a ref, stored as a sharded UUID, e.g.
 
 ----
-  refs/groups/ef/deafbeefdeafbeefdeafbeefdeafbeefdeafbeef
+  refs/groups/de/deafbeefdeafbeefdeafbeefdeafbeefdeafbeef
 ----
 
 The ref points to commits holding files. The files are
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 8239d62..b1f9912 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -9,14 +9,15 @@
 
 [options="header"]
 |=================================================
-|Description          | Default Query
-|All > Open           | status:open '(or is:open)'
-|All > Merged         | status:merged
-|All > Abandoned      | status:abandoned
-|My > Watched Changes | is:watched is:open
-|My > Starred Changes | is:starred
-|My > Draft Comments  | has:draft
-|Open changes in Foo  | status:open project:Foo
+|Description            | Default Query
+|Changes > Open         | status:open '(or is:open)'
+|Changes > Merged       | status:merged
+|Changes > Abandoned    | status:abandoned
+|Your > Watched Changes | is:watched is:open
+|Your > Starred Changes | is:starred
+|Your > Draft Comments  | has:draft
+|Your > Edits           | has:edit
+|Open changes in Foo    | status:open project:Foo
 |=================================================
 
 
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
index e4cb239..0bb0578 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeUpdateTest.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.Address;
 import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.Change;
@@ -256,10 +257,12 @@
     return c;
   }
 
+  @CanIgnoreReturnValue
   private AttentionSetUpdate addToAttentionSet(ChangeUpdate update) {
     return addToAttentionSet(update, otherUser);
   }
 
+  @CanIgnoreReturnValue
   private AttentionSetUpdate addToAttentionSet(ChangeUpdate update, IdentifiedUser user) {
     AttentionSetUpdate attentionSetUpdate =
         AttentionSetUpdate.createForWrite(
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
index 9f8f930..62de868 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.ts
@@ -47,10 +47,19 @@
   adminViewModelToken,
   AdminViewState,
 } from '../../../models/views/admin';
-import {GroupDetailView, GroupViewState} from '../../../models/views/group';
-import {RepoDetailView, RepoViewState} from '../../../models/views/repo';
+import {
+  GroupDetailView,
+  groupViewModelToken,
+  GroupViewState,
+} from '../../../models/views/group';
+import {
+  RepoDetailView,
+  repoViewModelToken,
+  RepoViewState,
+} from '../../../models/views/repo';
 import {resolve} from '../../../models/dependency';
 import {subscribe} from '../../lit/subscription-controller';
+import {merge} from 'rxjs';
 
 const INTERNAL_GROUP_REGEX = /^[\da-f]{40}$/;
 
@@ -123,13 +132,22 @@
 
   private readonly restApiService = getAppContext().restApiService;
 
-  private readonly getViewModel = resolve(this, adminViewModelToken);
+  private readonly getAdminViewModel = resolve(this, adminViewModelToken);
+
+  private readonly getGroupViewModel = resolve(this, groupViewModelToken);
+
+  private readonly getRepoViewModel = resolve(this, repoViewModelToken);
 
   constructor() {
     super();
     subscribe(
       this,
-      () => this.getViewModel().state$,
+      () =>
+        merge(
+          this.getAdminViewModel().state$,
+          this.getGroupViewModel().state$,
+          this.getRepoViewModel().state$
+        ),
       x => {
         this.viewState = x;
         if (this.needsReload()) this.reload();
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
index 3ddcae4..81a29e3 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.ts
@@ -1779,12 +1779,7 @@
   }
 
   computeHasNewAttention(account?: AccountInfo) {
-    return !!(
-      account &&
-      ((account._account_id &&
-        this.newAttentionSet?.has(account._account_id)) ||
-        (account.email && this.newAttentionSet?.has(account.email)))
-    );
+    return !!(account && this.newAttentionSet?.has(getUserId(account)));
   }
 
   computeNewAttention() {
@@ -1813,7 +1808,7 @@
     const newAttention = new Set(this.currentAttentionSet);
 
     for (const user of this.mentionedUsersInUnresolvedDrafts) {
-      newAttention.add(user.email!);
+      newAttention.add(getUserId(user));
     }
 
     if (this.change.status === ChangeStatus.NEW) {
@@ -1942,9 +1937,7 @@
   }
 
   findAccountById(userId: UserId) {
-    return this.allAccounts().find(
-      r => r._account_id === userId || r.email === userId
-    );
+    return this.allAccounts().find(r => getUserId(r) === userId);
   }
 
   allAccounts() {
@@ -2148,7 +2141,7 @@
   private alreadyExists(ccs: AccountInput[], user: AccountInfoInput) {
     return ccs
       .filter(cc => isAccount(cc))
-      .some(cc => (cc as AccountInfoInput).email === user.email);
+      .some(cc => getUserId(cc) === getUserId(user));
   }
 
   private isAlreadyReviewerOrCC(user: AccountInfo) {
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 acf4d13..f33f7d8 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -8,10 +8,9 @@
 import {votingStyles} from '../../../styles/gr-voting-styles';
 import {css, html, LitElement, nothing, PropertyValues} from 'lit';
 import {customElement, property} from 'lit/decorators.js';
-import {getEventPath, Key} from '../../../utils/dom-util';
+import {addShortcut, getEventPath, Key} from '../../../utils/dom-util';
 import {getAppContext} from '../../../services/app-context';
 import {classMap} from 'lit/directives/class-map.js';
-import {ShortcutController} from '../../lit/shortcut-controller';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -56,8 +55,6 @@
   @property({type: Boolean, reflect: true})
   disabled: boolean | null = null;
 
-  private readonly shortcuts = new ShortcutController(this);
-
   static override get styles() {
     return [
       votingStyles,
@@ -211,8 +208,8 @@
     super();
     this.initialTabindex = this.getAttribute('tabindex') || '0';
     this.addEventListener('click', e => this._handleAction(e));
-    this.shortcuts.addLocal({key: Key.ENTER}, () => this.click());
-    this.shortcuts.addLocal({key: Key.SPACE}, () => this.click());
+    addShortcut(this, {key: Key.ENTER}, () => this.click());
+    addShortcut(this, {key: Key.SPACE}, () => this.click());
   }
 
   override updated(changedProperties: PropertyValues) {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index 3887ee5b..eab6638 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -34,7 +34,7 @@
 
 export const MERGE_CONFLICT_TOOLTIP =
   'This change has merge conflicts. ' +
-  'Download the patch and run "git rebase". ' +
+  'Rebase on the upstream branch (e.g. "git pull --rebase"). ' +
   'Upload a new patchset after resolving all merge conflicts.';
 
 export const GIT_CONFLICT_TOOLTIP =
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 f5e8a1a..cc76150 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -501,6 +501,9 @@
         .draft gr-account-label {
           width: unset;
         }
+        .draft gr-formatted-text.message {
+          margin-bottom: var(--spacing-m);
+        }
         .portedMessage {
           margin: 0 var(--spacing-m);
         }
@@ -723,7 +726,6 @@
         class="message"
         .content=${this.comment?.message}
         .config=${this.commentLinks}
-        ?noTrailingMargin=${!isDraftOrUnsaved(this.comment)}
       ></gr-formatted-text>
     `;
   }
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 2e4383e..5686c6b 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
@@ -141,10 +141,7 @@
               </div>
             </div>
             <div class="body">
-              <gr-formatted-text
-                class="message"
-                notrailingmargin=""
-              ></gr-formatted-text>
+              <gr-formatted-text class="message"></gr-formatted-text>
             </div>
           </div>
         `
@@ -178,10 +175,7 @@
             </div>
             <div class="body">
               <div class="robotId"></div>
-              <gr-formatted-text
-                class="message"
-                notrailingmargin=""
-              ></gr-formatted-text>
+              <gr-formatted-text class="message"></gr-formatted-text>
               <div class="robotActions">
                 <gr-icon
                   icon="link"
diff --git a/polygerrit-ui/app/models/views/repo.ts b/polygerrit-ui/app/models/views/repo.ts
index 93064fd..02fd17d 100644
--- a/polygerrit-ui/app/models/views/repo.ts
+++ b/polygerrit-ui/app/models/views/repo.ts
@@ -27,10 +27,6 @@
   offset?: number | string;
 }
 
-const DEFAULT_STATE: RepoViewState = {
-  view: GerritView.REPO,
-};
-
 export function createRepoUrl(state: Omit<RepoViewState, 'view'>) {
   let url = `/admin/repos/${encodeURL(`${state.repo}`, true)}`;
   if (state.detail === RepoDetailView.GENERAL) {
@@ -51,8 +47,8 @@
 
 export const repoViewModelToken = define<RepoViewModel>('repo-view-model');
 
-export class RepoViewModel extends Model<RepoViewState> {
+export class RepoViewModel extends Model<RepoViewState | undefined> {
   constructor() {
-    super(DEFAULT_STATE);
+    super(undefined);
   }
 }
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index 106f1f3..33b9176 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -16,8 +16,7 @@
   LifeCycle,
   Timing,
 } from '../../constants/reporting';
-import {getCLS, getFID, getLCP} from 'web-vitals';
-import {Metric} from 'web-vitals/src/types';
+import {getCLS, getFID, getLCP, Metric} from 'web-vitals';
 
 // Latency reporting constants.