Fix duplicate autosubmit flow creation on view navigation

In PolyGerrit flows, getChangePrefix() used window.location.pathname
directly without stripping view subpaths. When users checked autosubmit
on a specific patchset, diff range (e.g. /9..10), or file view, the
autosubmit condition included that view suffix.

Navigating to another view caused getChangePrefix() to evaluate to a
different URL prefix, making hasAutosubmitFlowAlready() return false.
This incorrectly unchecked the autosubmit checkbox and created duplicate
flows upon re-checking.

Normalize getChangePrefix using CHANGE_PREFIX_PATTERN to strip patchset,
diff range, comment ID, and file path subpaths from
window.location.pathname.

Release-Notes: skip
Google-Bug-Id: b/482366559
Change-Id: I89fa0ab862d760f5c48ead85e17aabc70e1d2acf
diff --git a/polygerrit-ui/app/models/flows/flows-model.ts b/polygerrit-ui/app/models/flows/flows-model.ts
index 300a1b7..83d0c74 100644
--- a/polygerrit-ui/app/models/flows/flows-model.ts
+++ b/polygerrit-ui/app/models/flows/flows-model.ts
@@ -31,12 +31,26 @@
 
 export const SUBMIT_ACTION_NAME = 'submit';
 
+/**
+ * Matches the base change path prefix up to the change number (e.g. `/c/project/+/123`),
+ * ignoring any trailing patchsets, diff ranges, comment IDs, or file paths.
+ *
+ * @see RoutePattern.CHANGE in `gr-router.ts` for corresponding route pattern.
+ */
+const CHANGE_PREFIX_PATTERN = /^(.*?\/(?:c\/.+?\/\+)\/\d+)/;
+
 export function getSubmitCondition() {
   return getChangePrefix() + ' is is:submittable';
 }
 
+/**
+ * Returns the change URL prefix (e.g. `http://host/c/project/+/123`), stripping
+ * patchset numbers, diff ranges, comment IDs, and file paths from pathname.
+ */
 export function getChangePrefix() {
-  return window.location.origin + window.location.pathname;
+  const match = window.location.pathname.match(CHANGE_PREFIX_PATTERN);
+  const pathname = match ? match[1] : window.location.pathname;
+  return window.location.origin + pathname;
 }
 
 export class FlowsModel extends Model<FlowsState> {
diff --git a/polygerrit-ui/app/models/flows/flows-model_test.ts b/polygerrit-ui/app/models/flows/flows-model_test.ts
index 2dfe5a7..18c9538 100644
--- a/polygerrit-ui/app/models/flows/flows-model_test.ts
+++ b/polygerrit-ui/app/models/flows/flows-model_test.ts
@@ -200,4 +200,69 @@
     await waitUntil(() => !flowsModel.hasAutosubmitFlowAlready());
     assert.isFalse(flowsModel.hasAutosubmitFlowAlready());
   });
+
+  suite('getChangePrefix', () => {
+    let originalPath: string;
+
+    setup(() => {
+      originalPath = window.location.pathname;
+    });
+
+    teardown(() => {
+      window.history.replaceState(null, '', originalPath);
+    });
+
+    test('strips patchset, diff range, and file path subpaths', () => {
+      const origin = window.location.origin;
+
+      window.history.replaceState(null, '', '/c/my-repo/+/123');
+      assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`);
+
+      window.history.replaceState(null, '', '/c/my-repo/+/123/4');
+      assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`);
+
+      window.history.replaceState(null, '', '/c/my-repo/+/123/1..4');
+      assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`);
+
+      window.history.replaceState(
+        null,
+        '',
+        '/c/my-repo/+/123/1..4/src/file.ts'
+      );
+      assert.equal(getChangePrefix(), `${origin}/c/my-repo/+/123`);
+    });
+  });
+
+  test('hasAutosubmitFlowAlready detects flows when navigating between patchsets/diff ranges', async () => {
+    const originalPath = window.location.pathname;
+    try {
+      window.history.replaceState(null, '', '/c/my-repo/+/123/4..5/src/foo.ts');
+      stubRestApi('getIfFlowsIsEnabled').resolves({enabled: true});
+      const expectedPrefix = `${window.location.origin}/c/my-repo/+/123`;
+      stubRestApi('listFlows').resolves([
+        createFlow({
+          uuid: 'flow1',
+          stages: [
+            {
+              expression: {
+                condition: `${expectedPrefix} is is:submittable`,
+                action: {name: SUBMIT_ACTION_NAME},
+              },
+              state: FlowStageState.DONE,
+            },
+          ],
+        }),
+      ]);
+
+      changeModel.updateStateChange({
+        ...createParsedChange(),
+        _number: 123 as NumericChangeId,
+      });
+      await waitUntil(() => flowsModel.getState().flows.length > 0);
+
+      assert.isTrue(flowsModel.hasAutosubmitFlowAlready());
+    } finally {
+      window.history.replaceState(null, '', originalPath);
+    }
+  });
 });